Fix clipping/eliding of multi-line task descriptions.
[kdepim.git] / messagelist / storagemodel.cpp
blob150d137fc8ab15d7715e1700d54ac3e3a3870365
1 /*
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>
51 namespace MessageList
54 class StorageModel::Private
56 public:
57 void onSourceDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight );
58 void onSelectionChanged();
59 void loadSettings();
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 )
70 : q( owner ),
71 mSopranoModel( new Soprano::Util::SignalCacheModel( Nepomuk::ResourceManager::instance()->mainModel() ) )
75 } // namespace MessageList
77 using namespace Akonadi;
78 using namespace MessageList;
80 namespace {
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 ) )
98 d->mModel = 0;
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()) );
147 d->loadSettings();
148 connect( Core::Settings::self(), SIGNAL(configChanged()),
149 this, SLOT(loadSettings()) );
153 StorageModel::~StorageModel()
155 delete d;
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>();
165 if ( c.isValid() ) {
166 collections << c;
170 return collections;
173 QString StorageModel::id() const
175 QStringList ids;
176 QModelIndexList indexes = d->mSelectionModel->selectedRows();
178 foreach ( const QModelIndex &index, indexes ) {
179 Collection c = index.data( EntityTreeModel::CollectionRole ).value<Collection>();
180 if ( c.isValid() ) {
181 ids << QString::number( c.id() );
185 ids.sort();
186 return ids.join(QLatin1String( ":" ));
189 bool StorageModel::isOutBoundFolder( const Akonadi::Collection& c ) const
191 if ( c.hasAttribute<MessageFolderAttribute>()
192 && c.attribute<MessageFolderAttribute>()->isOutboundFolder() ) {
193 return true;
195 return false;
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>();
204 if ( c.isValid() ) {
205 return isOutBoundFolder( c );
209 return false;
212 int StorageModel::initialUnreadRowCountGuess() const
214 QModelIndexList indexes = d->mSelectionModel->selectedRows();
216 int unreadCount = 0;
218 foreach ( const QModelIndex &index, indexes ) {
219 Collection c = index.data( EntityTreeModel::CollectionRole ).value<Collection>();
220 if ( c.isValid() ) {
221 unreadCount+= c.statistics().unreadCount();
225 return 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() ) {
243 sender = unknown;
245 if ( receiver.isEmpty() ) {
246 receiver = unknown;
249 mi->initialSetup( mail->date()->dateTime().toTime_t(),
250 item.size(),
251 sender, receiver,
252 bUseReceiver );
254 QString subject = mail->subject()->asUnicodeString();
255 if ( subject.isEmpty() ) {
256 subject = QLatin1Char( '(' ) + noSubject + QLatin1Char( ')' );
259 mi->setSubject( subject );
261 updateMessageItemData( mi, row );
263 return true;
266 static QByteArray md5Encode( const QByteArray &str )
268 if ( str.trimmed().isEmpty() ) return QByteArray();
270 QCryptographicHash c( QCryptographicHash::Md5 );
271 c.addData( str.trimmed() );
272 return c.result();
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...
281 switch ( subset ) {
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 );
288 // fall through
290 case PerfectThreadingPlusReferences:
291 if ( !mail->references()->identifiers().isEmpty() ) {
292 mi->setReferencesIdMD5( md5Encode( mail->references()->identifiers().first() ) );
294 // fall through
295 case PerfectThreadingOnly:
296 mi->setMessageIdMD5( md5Encode( mail->messageID()->identifier() ) );
297 if ( !mail->inReplyTo()->identifiers().isEmpty() ) {
298 mi->setInReplyToIdMD5( md5Encode( mail->inReplyTo()->identifiers().first() ) );
300 break;
301 default:
302 Q_ASSERT( false ); // should never happen
303 break;
307 void StorageModel::updateMessageItemData( MessageList::Core::MessageItem *mi,
308 int row ) const
310 const Item item = itemForRow( row );
311 const KMime::Message::Ptr mail = messageForItem( item );
312 Q_ASSERT( mail );
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 )
335 Q_UNUSED( mi );
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.
352 Q_UNUSED( index );
353 Q_UNUSED( role );
355 return QVariant();
358 int StorageModel::columnCount( const QModelIndex &parent ) const
360 if ( !parent.isValid() )
361 return 1;
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
375 Q_UNUSED( index );
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();
389 KUrl::List urls;
390 foreach ( MessageList::Core::MessageItem* mi, items ) {
391 Akonadi::Item item = itemForRow( mi->currentModelIndexRow() );
392 urls << item.url( Item::UrlWithMimeType );
395 urls.populateMimeData( data );
397 return 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() ) {
413 return;
416 const QModelIndexList list = mModel->match( QModelIndex(), EntityTreeModel::ItemIdRole, item.id() );
417 if ( list.isEmpty() ) {
418 return;
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 ) );
446 } else {
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() );
459 } else {
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()
480 reset();
483 #include "storagemodel.moc"