You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

637 lines
18 KiB

/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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 "messagelistview/storagemodel.h"
#include "messagelistview/core/messageitem.h"
#include <libkdepim/messagestatus.h>
#include <KDebug>
#include <KIconLoader>
#include <QList>
#include "kmfolder.h"
#include "kmfolderindex.h"
#include "kmmsgbase.h"
#include "kmmessagetag.h"
#include "kmkernel.h"
#include "kmmsgdict.h"
namespace KMail
{
namespace MessageListView
{
StorageModel::StorageModel( KMFolder * folder, QObject * parent )
: Core::StorageModel( parent ), mFolder( folder )
{
mFolder->open( "MessageListView::StorageModel" );
// Add all items of the folder to the serial cache. This makes initalizing
// the message items much faster, since they no longer need to call the expensive
// KMFolderIndex::find() when trying to get the serial number.
if ( mFolder->storage() ) {
KMFolderIndex *index = dynamic_cast<KMFolderIndex*>( mFolder->storage() );
if ( index )
index->addToSerialCache();
}
#if 0
// This signal is unreliable, it's not emitted when quiet() is set on the folder...
// It's a shame since with the other signal we need to lookup the index which
// is actually known when it's emitted....
connect( mFolder, SIGNAL( msgAdded( int ) ),
SLOT( slotMessageAdded( int ) ) );
#endif
connect( mFolder, SIGNAL( msgAdded( KMFolder *, quint32 ) ),
SLOT( slotMessageAdded( KMFolder *, quint32 ) ) );
connect( mFolder, SIGNAL( msgRemoved( int, const QString & ) ),
SLOT( slotMessageRemoved( int ) ) );
connect( mFolder, SIGNAL( cleared() ),
SLOT( slotFolderCleared() ) );
connect( mFolder, SIGNAL( closed() ),
SLOT( slotFolderClosed() ) );
connect( mFolder, SIGNAL( expunged( KMFolder * ) ),
SLOT( slotFolderExpunged() ) );
connect( mFolder->storage(), SIGNAL( invalidated( KMFolder * ) ),
SLOT( slotFolderInvalidated() ) );
connect( mFolder, SIGNAL( nameChanged() ),
SLOT( slotFolderNameChanged() ) );
connect( mFolder, SIGNAL( msgHeaderChanged( KMFolder * , int ) ),
SLOT( slotMessageHeaderChanged( KMFolder *, int ) ) );
#if 0
// FIXME: Do we need to handle these remaining signals ?
/** Emitted when the status, name, or associated accounts of this
folder changed. */
void changed();
/** Emitted when the icon paths are set. */
void iconsChanged();
/** Emitted when the shortcut associated with this folder changes. */
void shortcutChanged( KMFolder * );
/** Emitted, when the status of a message is changed */
void msgChanged(KMFolder*, quint32 sernum, int delta);
/** Emmited to display a message somewhere in a status line. */
void statusMsg(const QString&);
/** Emitted when a folder was removed */
void removed(KMFolder*, bool);
/** Emitted when the variables for the config of the view have changed */
void viewConfigChanged();
#endif
mMessageCount = mFolder->count();
// Custom/System colors
KConfigGroup config( KMKernel::config(), "Reader" );
mColorNewMessage = QColor( "red" );
mColorUnreadMessage = QColor( "blue" );
mColorImportantMessage = QColor( 0x00, 0x7f, 0x00 );
mColorToDoMessage = QColor( 0x00, 0x98, 0x00 );
if ( !config.readEntry( "defaultColors", true ) )
{
mColorNewMessage = config.readEntry( "NewMessage", mColorNewMessage );
mColorUnreadMessage = config.readEntry( "UnreadMessage", mColorUnreadMessage );
mColorImportantMessage = config.readEntry( "FlagMessage", mColorImportantMessage );
mColorToDoMessage = config.readEntry( "TodoMessage", mColorToDoMessage );
}
}
StorageModel::~StorageModel()
{
// Disconnect anything that might trigger before we write index and close the folder...
#if 0
disconnect(
mFolder, SIGNAL( msgAdded( int ) ),
this, SLOT( slotMessageAdded( int ) )
);
#endif
disconnect(
mFolder, SIGNAL( msgAdded( KMFolder *, quint32 ) ),
this, SLOT( slotMessageAdded( KMFolder *, quint32 ) )
);
disconnect(
mFolder, SIGNAL( msgRemoved( int, const QString & ) ),
this, SLOT( slotMessageRemoved( int ) )
);
disconnect(
mFolder, SIGNAL( cleared() ),
this, SLOT( slotFolderCleared() )
);
disconnect(
mFolder, SIGNAL( closed() ),
this, SLOT( slotFolderClosed() )
);
disconnect(
mFolder, SIGNAL( expunged( KMFolder * ) ),
this, SLOT( slotFolderExpunged() )
);
disconnect(
mFolder, SIGNAL( nameChanged() ),
this, SLOT( slotFolderNameChanged() )
);
disconnect(
mFolder, SIGNAL( msgHeaderChanged( KMFolder * , int ) ),
this, SLOT( slotMessageHeaderChanged( KMFolder *, int ) )
);
//mFolder->markNewAsUnread(); <-- do we REALLY need to do this ?.. couldn't we use a timed-expire instead ?
if ( mFolder->dirty() )
mFolder->writeIndex(); // this is straight from KMHeaders...
mFolder->close( "MessageListView::StorageModel" );
}
void StorageModel::releaseMessage( int row ) const
{
Q_ASSERT( row >= 0 );
if( row >= mFolder->count() )
kWarning() << "Trying to release a message at row " << row << " that no longer exists in the folder";
KMMessage * msg = mFolder->getMsg( row );
if ( !msg )
return;
if ( msg->transferInProgress() )
return;
mFolder->ignoreJobsForMessage( msg );
mFolder->unGetMsg( row );
}
KMMessage * StorageModel::message( int row ) const
{
Q_ASSERT( row >= 0 );
Q_ASSERT( row < mFolder->count() );
return mFolder->getMsg( row ); // this CAN fail!
}
KMMessage * StorageModel::message( Core::MessageItem * mi ) const
{
if ( !mi->isValid() )
return 0;
// FIXME: KMHeaders seem to _attempt_ to apply an unGetMsg()
// on the previously queried message item.
// The fact is that NOT ALL of the KMMessages requested
// get a corresponding unGetMsg(). It's also very unclear
// when the unGetMsg() should happen since there seem
// to be (documented by comments) conditions in that
// the unGetMsg() causes crashes in other parts of KMail
// that still reference the KMMessage object. This is broken
// and should be substituted by a proper reference counting
// method. Anyone who wishes to keep the message for more
// than one Qt event processing step should ref() the message.
// The folder storage, then, should have a housekeeping
// mechanism (timer which sweeps away the unreferenced objects).
return message( mi->currentModelIndexRow() );
}
KMMsgBase * StorageModel::msgBase( int row ) const
{
Q_ASSERT( row >= 0 );
Q_ASSERT( row < mFolder->count() );
return mFolder->getMsgBase( row );
}
KMMsgBase * StorageModel::msgBase( Core::MessageItem * mi ) const
{
if ( !mi->isValid() )
return 0;
return msgBase( mi->currentModelIndexRow() );
}
int StorageModel::msgBaseRow( KMMsgBase * msgBase )
{
return mFolder->find( msgBase );
}
QString StorageModel::id() const
{
return mFolder->idString();
}
bool StorageModel::containsOutboundMessages() const
{
return mFolder->whoField().toLower() == "to";
}
bool StorageModel::initializeMessageItem( Core::MessageItem * mi, int row, bool bUseReceiver ) const
{
KMMsgBase * msg = mFolder->getMsgBase( row );
if ( !msg )
return false;
QString sender = msg->fromStrip();
QString receiver = msg->toStrip();
mi->initialSetup(
msg->date(), msg->msgSize(),
sender, receiver,
bUseReceiver ? receiver : sender
);
mi->setUniqueId( msg->getMsgSerNum() );
KPIM::MessageStatus stat = msg->messageStatus();
mi->setSubjectAndStatus(
msg->subject(),
stat
);
QColor clr;
// FIXME: Tags should be sorted by priority!
if ( msg->tagList() )
{
if ( !msg->tagList()->isEmpty() )
{
int bestPriority = -0xfffff;
QList< Core::MessageItem::Tag * > * tagList = new QList< Core::MessageItem::Tag * >();
for ( KMMessageTagList::Iterator it = msg->tagList()->begin(); it != msg->tagList()->end(); ++it )
{
const KMMessageTagDescription * description = kmkernel->msgTagMgr()->find( *it );
if ( description )
{
if ( ( bestPriority < description->priority() ) || ( !clr.isValid() ) )
{
clr = description->textColor();
bestPriority = description->priority();
}
Core::MessageItem::Tag * tag;
if ( description->toolbarIconName().isEmpty() )
tag = new Core::MessageItem::Tag( SmallIcon( "feed-subscribe" ), description->name(), *it );
else
tag = new Core::MessageItem::Tag( SmallIcon( description->toolbarIconName() ), description->name(), *it );
tagList->append( tag );
}
}
if ( tagList->isEmpty() )
delete tagList;
else
mi->setTagList( tagList );
}
}
switch ( msg->encryptionState() )
{
case KMMsgFullyEncrypted:
mi->setEncryptionState( Core::MessageItem::FullyEncrypted );
break;
case KMMsgPartiallyEncrypted:
mi->setEncryptionState( Core::MessageItem::PartiallyEncrypted );
break;
case KMMsgEncryptionStateUnknown:
mi->setEncryptionState( Core::MessageItem::EncryptionStateUnknown );
break;
default:
mi->setEncryptionState( Core::MessageItem::NotEncrypted );
break;
}
switch ( msg->signatureState() )
{
case KMMsgFullySigned:
mi->setSignatureState( Core::MessageItem::FullySigned );
break;
case KMMsgPartiallySigned:
mi->setSignatureState( Core::MessageItem::PartiallySigned );
break;
case KMMsgSignatureStateUnknown:
mi->setSignatureState( Core::MessageItem::SignatureStateUnknown );
break;
default:
mi->setSignatureState( Core::MessageItem::NotSigned );
break;
}
if ( !clr.isValid() )
{
if ( stat.isNew() )
clr = mColorNewMessage;
else if ( stat.isUnread() )
clr = mColorUnreadMessage;
else if ( stat.isImportant() )
clr = mColorImportantMessage;
else if ( stat.isToAct() )
clr = mColorToDoMessage;
}
if ( clr.isValid() )
mi->setTextColor( clr );
return true;
}
void StorageModel::updateMessageItemData( Core::MessageItem * mi, int row ) const
{
KMMsgBase * msg = mFolder->getMsgBase( row );
Q_ASSERT( msg ); // We ASSUME that initializeMessageItem has been called succesfuly...
bool dateDiffers = mi->date() != msg->date();
if ( dateDiffers )
{
mi->setDate( msg->date() );
mi->recomputeMaxDate();
}
QColor clr;
KPIM::MessageStatus stat = msg->messageStatus();
mi->setStatus( stat );
switch ( msg->encryptionState() )
{
case KMMsgFullyEncrypted:
mi->setEncryptionState( Core::MessageItem::FullyEncrypted );
break;
case KMMsgPartiallyEncrypted:
mi->setEncryptionState( Core::MessageItem::PartiallyEncrypted );
break;
case KMMsgEncryptionStateUnknown:
case KMMsgEncryptionProblematic:
mi->setEncryptionState( Core::MessageItem::EncryptionStateUnknown );
break;
default:
mi->setEncryptionState( Core::MessageItem::NotEncrypted );
break;
}
switch ( msg->signatureState() )
{
case KMMsgFullySigned:
mi->setSignatureState( Core::MessageItem::FullySigned );
break;
case KMMsgPartiallySigned:
mi->setSignatureState( Core::MessageItem::PartiallySigned );
break;
case KMMsgSignatureStateUnknown:
case KMMsgSignatureProblematic:
mi->setSignatureState( Core::MessageItem::SignatureStateUnknown );
break;
default:
mi->setSignatureState( Core::MessageItem::NotSigned );
break;
}
QList< Core::MessageItem::Tag * > * tagList = 0;
if ( msg->tagList() )
{
if ( !msg->tagList()->isEmpty() )
{
int bestPriority = -0xfffff;
tagList = new QList< Core::MessageItem::Tag * >();
for ( KMMessageTagList::Iterator it = msg->tagList()->begin(); it != msg->tagList()->end(); ++it )
{
const KMMessageTagDescription * description = kmkernel->msgTagMgr()->find( *it );
if ( description )
{
if ( ( bestPriority < description->priority() ) || ( !clr.isValid() ) )
{
clr = description->textColor();
bestPriority = description->priority();
}
Core::MessageItem::Tag * tag;
if ( description->toolbarIconName().isEmpty() )
tag = new Core::MessageItem::Tag( SmallIcon( "feed-subscribe" ), description->name(), *it );
else
tag = new Core::MessageItem::Tag( SmallIcon( description->toolbarIconName() ), description->name(), *it );
tagList->append( tag );
}
}
if ( tagList->isEmpty() )
{
delete tagList;
tagList = 0;
}
}
}
mi->setTagList( tagList );
if ( !clr.isValid() )
{
if ( stat.isNew() )
clr = mColorNewMessage;
else if ( stat.isUnread() )
clr = mColorUnreadMessage;
else if ( stat.isImportant() )
clr = mColorImportantMessage;
else if ( stat.isToAct() )
clr = mColorToDoMessage;
}
mi->setTextColor( clr ); // set even if invalid (->default color)
// FIXME: Handle MDN State ?
}
void StorageModel::fillMessageItemThreadingData( Core::MessageItem * mi, int row, ThreadingDataSubset subset ) const
{
KMMsgBase * msg = mFolder->getMsgBase( row );
Q_ASSERT( msg ); // We ASSUME that initializeMessageItem has been called succesfuly...
switch ( subset )
{
case PerfectThreadingReferencesAndSubject:
mi->setStrippedSubjectMD5( msg->strippedSubjectMD5() );
if ( mi->strippedSubjectMD5().isEmpty() )
{
msg->initStrippedSubjectMD5();
mi->setStrippedSubjectMD5( msg->strippedSubjectMD5() );
}
mi->setSubjectIsPrefixed( msg->subjectIsPrefixed() && !msg->subject().isEmpty() );
// fall through
case PerfectThreadingPlusReferences:
mi->setReferencesIdMD5( msg->replyToAuxIdMD5() );
// fall through
case PerfectThreadingOnly:
mi->setMessageIdMD5( msg->msgIdMD5() );
mi->setInReplyToIdMD5( msg->replyToIdMD5() );
break;
default:
Q_ASSERT( false ); // should never happen
break;
};
}
void StorageModel::setMessageItemStatus( Core::MessageItem * mi, const KPIM::MessageStatus &status )
{
KMMsgBase * msg = msgBase( mi );
if ( !msg )
return; // This can be called at a really later stage (with respect to the initial fill).
// Assume that something wrong may be happened to the folder in the meantime...
msg->setStatus( status );
}
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 & ) const
{
return QModelIndex(); // this model is flat.
}
int StorageModel::rowCount( const QModelIndex &parent ) const
{
if ( !parent.isValid() )
return mMessageCount;
return 0; // this model is flat.
}
int StorageModel::initialUnreadRowCountGuess() const
{
return mFolder->countUnread();
}
void StorageModel::slotFolderClosed()
{
// Reopen the folder, then reset the model.
mFolder->open( "MessageListView::StorageModel" );
slotFolderCleared();
}
void StorageModel::slotFolderCleared()
{
mMessageCount = mFolder->count();
reset();
}
void StorageModel::slotFolderExpunged()
{
slotFolderClosed();
}
void StorageModel::slotFolderInvalidated()
{
kDebug() << "Folder invalidated.";
slotFolderCleared();
}
void StorageModel::slotFolderNameChanged()
{
}
void StorageModel::slotMessageHeaderChanged( KMFolder *folder, int idx )
{
Q_ASSERT( folder == mFolder );
QModelIndex modelIndex = createIndex( idx, 0 );
emit dataChanged( modelIndex, modelIndex );
}
void StorageModel::slotMessageAdded( KMFolder *folder, quint32 sernum )
{
Q_ASSERT( folder == mFolder );
KMFolder * retFolder;
int idx;
KMMsgDict::instance()->getLocation( sernum, &retFolder, &idx );
// retFolder is 0 in case of search folders
Q_ASSERT( retFolder == folder || !retFolder );
Q_ASSERT( idx != -1 );
beginInsertRows( QModelIndex(), idx, idx );
mMessageCount++;
endInsertRows();
}
void StorageModel::slotMessageRemoved( int idx )
{
beginRemoveRows( QModelIndex(), idx, idx );
mMessageCount--;
endRemoveRows();
}
} // namespace MessageListView
} // namespace KMail