2 Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include "storagemodel.h"
21 #include <messagecore/stringutil.h>
22 #include <messagecore/globalsettings.h>
23 #include <messagecore/nodehelper.h>
25 #include <akonadi/attributefactory.h>
26 #include <akonadi/collection.h>
27 #include <akonadi/collectionstatistics.h>
28 #include <akonadi/entitymimetypefiltermodel.h>
29 #include <akonadi/entitytreemodel.h>
30 #include <akonadi/item.h>
31 #include <akonadi/itemmodifyjob.h>
32 #include <akonadi/kmime/messagefolderattribute.h>
33 #include <akonadi/selectionproxymodel.h>
35 #include <KDE/KLocale>
36 #include <Nepomuk/ResourceManager>
37 #include <Soprano/Statement>
38 #include <soprano/signalcachemodel.h>
39 #include <soprano/nao.h>
41 #include "core/messageitem.h"
42 #include "core/settings.h"
44 #include <QtCore/QAbstractItemModel>
45 #include <QtCore/QAtomicInt>
46 #include <QtCore/QScopedPointer>
47 #include <QtGui/QItemSelectionModel>
48 #include <QtCore/QMimeData>
49 #include <QtCore/QCryptographicHash>
54 class StorageModel::Private
57 void onSourceDataChanged( const QModelIndex
&topLeft
, const QModelIndex
&bottomRight
);
58 void onSelectionChanged();
60 void statementChanged( const Soprano::Statement
&statement
);
62 StorageModel
* const q
;
64 QAbstractItemModel
*mModel
;
65 QItemSelectionModel
*mSelectionModel
;
67 QScopedPointer
<Soprano::Util::SignalCacheModel
> mSopranoModel
;
69 Private( StorageModel
*owner
)
71 mSopranoModel( new Soprano::Util::SignalCacheModel( Nepomuk::ResourceManager::instance()->mainModel() ) )
75 } // namespace MessageList
77 using namespace Akonadi
;
78 using namespace MessageList
;
82 KMime::Message::Ptr
messageForItem( const Akonadi::Item
&item
)
84 if ( !item
.hasPayload
<KMime::Message::Ptr
>() ) {
85 kWarning() << "Not a message" << item
.id() << item
.remoteId() << item
.mimeType();
86 return KMime::Message::Ptr();
88 return item
.payload
<KMime::Message::Ptr
>();
93 static QAtomicInt _k_attributeInitialized
;
95 StorageModel::StorageModel( QAbstractItemModel
*model
, QItemSelectionModel
*selectionModel
, QObject
*parent
)
96 : Core::StorageModel( parent
), d( new Private( this ) )
99 d
->mSelectionModel
= selectionModel
;
100 if ( _k_attributeInitialized
.testAndSetAcquire( 0, 1 ) ) {
101 AttributeFactory::registerAttribute
<MessageFolderAttribute
>();
104 Akonadi::SelectionProxyModel
*childrenFilter
= new Akonadi::SelectionProxyModel( d
->mSelectionModel
, this );
105 childrenFilter
->setSourceModel( model
);
106 childrenFilter
->setFilterBehavior( KSelectionProxyModel::ChildrenOfExactSelection
);
108 EntityMimeTypeFilterModel
*itemFilter
= new EntityMimeTypeFilterModel( this );
109 itemFilter
->setSourceModel( childrenFilter
);
110 itemFilter
->addMimeTypeExclusionFilter( Collection::mimeType() );
111 itemFilter
->addMimeTypeInclusionFilter( QLatin1String( "message/rfc822" ) );
112 itemFilter
->setHeaderGroup( EntityTreeModel::ItemListHeaders
);
114 d
->mModel
= itemFilter
;
116 kDebug() << "Using model:" << model
->metaObject()->className();
117 connect( d
->mSopranoModel
.data(), SIGNAL(statementAdded(Soprano::Statement
)),
118 SLOT(statementChanged(Soprano::Statement
)) );
119 connect( d
->mSopranoModel
.data(), SIGNAL(statementRemoved(Soprano::Statement
)),
120 SLOT(statementChanged(Soprano::Statement
)) );
122 connect( d
->mModel
, SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
123 this, SLOT(onSourceDataChanged(QModelIndex
,QModelIndex
)) );
125 connect( d
->mModel
, SIGNAL(layoutAboutToBeChanged()),
126 this, SIGNAL(layoutAboutToBeChanged()) );
127 connect( d
->mModel
, SIGNAL(layoutChanged()),
128 this, SIGNAL(layoutChanged()) );
129 connect( d
->mModel
, SIGNAL(modelAboutToBeReset()),
130 this, SIGNAL(modelAboutToBeReset()) );
131 connect( d
->mModel
, SIGNAL(modelReset()),
132 this, SIGNAL(modelReset()) );
134 //Here we assume we'll always get QModelIndex() in the parameters
135 connect( d
->mModel
, SIGNAL(rowsAboutToBeInserted(QModelIndex
,int,int)),
136 this, SIGNAL(rowsAboutToBeInserted(QModelIndex
,int,int)) );
137 connect( d
->mModel
, SIGNAL(rowsInserted(QModelIndex
,int,int)),
138 this, SIGNAL(rowsInserted(QModelIndex
,int,int)) );
139 connect( d
->mModel
, SIGNAL(rowsAboutToBeRemoved(QModelIndex
,int,int)),
140 this, SIGNAL(rowsAboutToBeRemoved(QModelIndex
,int,int)) );
141 connect( d
->mModel
, SIGNAL(rowsRemoved(QModelIndex
,int,int)),
142 this, SIGNAL(rowsRemoved(QModelIndex
,int,int)) );
144 connect( d
->mSelectionModel
, SIGNAL(selectionChanged(QItemSelection
,QItemSelection
)),
145 this, SLOT(onSelectionChanged()) );
148 connect( Core::Settings::self(), SIGNAL(configChanged()),
149 this, SLOT(loadSettings()) );
153 StorageModel::~StorageModel()
158 Collection::List
StorageModel::displayedCollections() const
160 Collection::List collections
;
161 QModelIndexList indexes
= d
->mSelectionModel
->selectedRows();
163 foreach ( const QModelIndex
&index
, indexes
) {
164 Collection c
= index
.data( EntityTreeModel::CollectionRole
).value
<Collection
>();
173 QString
StorageModel::id() const
176 QModelIndexList indexes
= d
->mSelectionModel
->selectedRows();
178 foreach ( const QModelIndex
&index
, indexes
) {
179 Collection c
= index
.data( EntityTreeModel::CollectionRole
).value
<Collection
>();
181 ids
<< QString::number( c
.id() );
186 return ids
.join(QLatin1String( ":" ));
189 bool StorageModel::isOutBoundFolder( const Akonadi::Collection
& c
) const
191 if ( c
.hasAttribute
<MessageFolderAttribute
>()
192 && c
.attribute
<MessageFolderAttribute
>()->isOutboundFolder() ) {
198 bool StorageModel::containsOutboundMessages() const
200 QModelIndexList indexes
= d
->mSelectionModel
->selectedRows();
202 foreach ( const QModelIndex
&index
, indexes
) {
203 Collection c
= index
.data( EntityTreeModel::CollectionRole
).value
<Collection
>();
205 return isOutBoundFolder( c
);
212 int StorageModel::initialUnreadRowCountGuess() const
214 QModelIndexList indexes
= d
->mSelectionModel
->selectedRows();
218 foreach ( const QModelIndex
&index
, indexes
) {
219 Collection c
= index
.data( EntityTreeModel::CollectionRole
).value
<Collection
>();
221 unreadCount
+= c
.statistics().unreadCount();
228 bool StorageModel::initializeMessageItem( MessageList::Core::MessageItem
*mi
,
229 int row
, bool bUseReceiver
) const
231 const Item item
= itemForRow( row
);
232 const KMime::Message::Ptr mail
= messageForItem( item
);
233 if ( !mail
) return false;
235 QString sender
= mail
->from()->asUnicodeString();
236 QString receiver
= mail
->to()->asUnicodeString();
238 // Static for speed reasons
239 static const QString noSubject
= i18nc( "displayed as subject when the subject of a mail is empty", "No Subject" );
240 static const QString
unknown( i18nc( "displayed when a mail has unknown sender, receiver or date", "Unknown" ) );
242 if ( sender
.isEmpty() ) {
245 if ( receiver
.isEmpty() ) {
249 mi
->initialSetup( mail
->date()->dateTime().toTime_t(),
254 QString subject
= mail
->subject()->asUnicodeString();
255 if ( subject
.isEmpty() ) {
256 subject
= QLatin1Char( '(' ) + noSubject
+ QLatin1Char( ')' );
259 mi
->setSubject( subject
);
261 updateMessageItemData( mi
, row
);
266 static QByteArray
md5Encode( const QByteArray
&str
)
268 if ( str
.trimmed().isEmpty() ) return QByteArray();
270 QCryptographicHash
c( QCryptographicHash::Md5
);
271 c
.addData( str
.trimmed() );
275 void StorageModel::fillMessageItemThreadingData( MessageList::Core::MessageItem
*mi
,
276 int row
, ThreadingDataSubset subset
) const
278 const KMime::Message::Ptr mail
= messageForRow( row
);
279 Q_ASSERT( mail
); // We ASSUME that initializeMessageItem has been called successfully...
282 case PerfectThreadingReferencesAndSubject
:
284 const QString subject
= mail
->subject()->asUnicodeString();
285 const QString strippedSubject
= MessageCore::StringUtil::stripOffPrefixes( subject
);
286 mi
->setStrippedSubjectMD5( md5Encode( strippedSubject
.toUtf8() ) );
287 mi
->setSubjectIsPrefixed( subject
!= strippedSubject
);
290 case PerfectThreadingPlusReferences
:
291 if ( !mail
->references()->identifiers().isEmpty() ) {
292 mi
->setReferencesIdMD5( md5Encode( mail
->references()->identifiers().first() ) );
295 case PerfectThreadingOnly
:
296 mi
->setMessageIdMD5( md5Encode( mail
->messageID()->identifier() ) );
297 if ( !mail
->inReplyTo()->identifiers().isEmpty() ) {
298 mi
->setInReplyToIdMD5( md5Encode( mail
->inReplyTo()->identifiers().first() ) );
302 Q_ASSERT( false ); // should never happen
307 void StorageModel::updateMessageItemData( MessageList::Core::MessageItem
*mi
,
310 const Item item
= itemForRow( row
);
311 const KMime::Message::Ptr mail
= messageForItem( item
);
314 Akonadi::MessageStatus stat
;
315 stat
.setStatusFromFlags( item
.flags() );
317 mi
->setAkonadiItem( item
);
318 mi
->setStatus( stat
);
320 mi
->setEncryptionState( Core::MessageItem::EncryptionStateUnknown
);
321 if ( stat
.isEncrypted() )
322 mi
->setEncryptionState( Core::MessageItem::FullyEncrypted
);
324 mi
->setSignatureState( Core::MessageItem::SignatureStateUnknown
);
325 if ( stat
.isSigned() )
326 mi
->setSignatureState( Core::MessageItem::FullySigned
);
328 mi
->invalidateTagCache();
329 mi
->invalidateAnnotationCache();
332 void StorageModel::setMessageItemStatus( MessageList::Core::MessageItem
*mi
,
333 int row
, const Akonadi::MessageStatus
&status
)
336 Item item
= itemForRow( row
);
337 item
.setFlags( status
.statusFlags() );
338 ItemModifyJob
*job
= new ItemModifyJob( item
, this );
339 job
->setIgnorePayload( true );
342 QVariant
StorageModel::data( const QModelIndex
&index
, int role
) const
344 // We don't provide an implementation for data() in No-Akonadi-KMail.
345 // This is because StorageModel must be a wrapper anyway (because columns
346 // must be re-mapped and functions not available in a QAbstractItemModel
347 // are strictly needed. So when porting to Akonadi this class will
348 // either wrap or subclass the MessageModel and implement initializeMessageItem()
349 // with appropriate calls to data(). And for No-Akonadi-KMail we still have
350 // a somewhat efficient implementation.
358 int StorageModel::columnCount( const QModelIndex
&parent
) const
360 if ( !parent
.isValid() )
362 return 0; // this model is flat.
365 QModelIndex
StorageModel::index( int row
, int column
, const QModelIndex
&parent
) const
367 if ( !parent
.isValid() )
368 return createIndex( row
, column
, 0 );
370 return QModelIndex(); // this model is flat.
373 QModelIndex
StorageModel::parent( const QModelIndex
&index
) const
376 return QModelIndex(); // this model is flat.
379 int StorageModel::rowCount( const QModelIndex
&parent
) const
381 if ( !parent
.isValid() )
382 return d
->mModel
->rowCount();
383 return 0; // this model is flat.
386 QMimeData
* StorageModel::mimeData( QList
< MessageList::Core::MessageItem
* > items
) const
388 QMimeData
*data
= new QMimeData();
390 foreach ( MessageList::Core::MessageItem
* mi
, items
) {
391 Akonadi::Item item
= itemForRow( mi
->currentModelIndexRow() );
392 urls
<< item
.url( Item::UrlWithMimeType
);
395 urls
.populateMimeData( data
);
401 void StorageModel::prepareForScan()
406 void StorageModel::Private::statementChanged( const Soprano::Statement
&statement
)
408 if ( statement
.predicate() == Soprano::Vocabulary::NAO::hasTag() ||
409 statement
.predicate() == Soprano::Vocabulary::NAO::description() )
411 const Akonadi::Item item
= Item::fromUrl( statement
.subject().uri() );
412 if ( !item
.isValid() ) {
416 const QModelIndexList list
= mModel
->match( QModelIndex(), EntityTreeModel::ItemIdRole
, item
.id() );
417 if ( list
.isEmpty() ) {
420 emit q
->dataChanged( q
->index( list
.first().row(), 0 ),
421 q
->index( list
.first().row(), 0 ) );
425 void StorageModel::Private::onSourceDataChanged( const QModelIndex
&topLeft
, const QModelIndex
&bottomRight
)
427 emit q
->dataChanged( q
->index( topLeft
.row(), 0 ),
428 q
->index( bottomRight
.row(), 0 ) );
431 void StorageModel::Private::onSelectionChanged()
433 emit q
->headerDataChanged( Qt::Horizontal
, 0, q
->columnCount()-1 );
436 void StorageModel::Private::loadSettings()
438 // Custom/System colors
439 Core::Settings
*settings
= Core::Settings::self();
441 if ( MessageCore::GlobalSettings::self()->useDefaultColors() ) {
442 Core::MessageItem::setNewMessageColor( QColor( "red" ) );
443 Core::MessageItem::setUnreadMessageColor( QColor( "blue" ) );
444 Core::MessageItem::setImportantMessageColor( QColor( 0x0, 0x7F, 0x0 ) );
445 Core::MessageItem::setToDoMessageColor( QColor( 0x0, 0x98, 0x0 ) );
447 Core::MessageItem::setNewMessageColor( settings
->newMessageColor() );
448 Core::MessageItem::setUnreadMessageColor( settings
->unreadMessageColor() );
449 Core::MessageItem::setImportantMessageColor( settings
->importantMessageColor() );
450 Core::MessageItem::setToDoMessageColor( settings
->todoMessageColor() );
453 if ( MessageCore::GlobalSettings::self()->useDefaultFonts() ) {
454 Core::MessageItem::setGeneralFont( KGlobalSettings::generalFont() );
455 Core::MessageItem::setNewMessageFont( KGlobalSettings::generalFont() );
456 Core::MessageItem::setUnreadMessageFont( KGlobalSettings::generalFont() );
457 Core::MessageItem::setImportantMessageFont( KGlobalSettings::generalFont() );
458 Core::MessageItem::setToDoMessageFont( KGlobalSettings::generalFont() );
460 Core::MessageItem::setGeneralFont( settings
->messageListFont() );
461 Core::MessageItem::setNewMessageFont( settings
->newMessageFont() );
462 Core::MessageItem::setUnreadMessageFont( settings
->unreadMessageFont() );
463 Core::MessageItem::setImportantMessageFont( settings
->importantMessageFont() );
464 Core::MessageItem::setToDoMessageFont( settings
->todoMessageFont() );
468 Item
StorageModel::itemForRow( int row
) const
470 return d
->mModel
->data( d
->mModel
->index( row
, 0 ), EntityTreeModel::ItemRole
).value
<Item
>();
473 KMime::Message::Ptr
StorageModel::messageForRow( int row
) const
475 return messageForItem( itemForRow( row
) );
478 void StorageModel::resetModelStorage()
483 #include "storagemodel.moc"