2
Copyright (c) 2008 Stephen Kelly <steveire@gmail.com>
4
This library is free software; you can redistribute it and/or modify it
5
under the terms of the GNU Library General Public License as published by
6
the Free Software Foundation; either version 2 of the License, or (at your
7
option) any later version.
9
This library is distributed in the hope that it will be useful, but WITHOUT
10
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12
License for more details.
14
You should have received a copy of the GNU Library General Public License
15
along with this library; see the file COPYING.LIB. If not, write to the
16
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20
#include "entitytreemodel_p.h"
21
#include "entitytreemodel.h"
23
#include <KDE/KIconLoader>
26
#include <akonadi/agentmanager.h>
27
#include <akonadi/agenttype.h>
28
#include <akonadi/collectionfetchjob.h>
29
#include <akonadi/collectionstatistics.h>
30
#include <akonadi/collectionstatisticsjob.h>
31
#include <akonadi/entitydisplayattribute.h>
32
#include <akonadi/itemfetchjob.h>
33
#include <akonadi/monitor.h>
34
#include <akonadi/session.h>
36
#include "collectionchildorderattribute.h"
40
using namespace Akonadi;
42
EntityTreeModelPrivate::EntityTreeModelPrivate( EntityTreeModel *parent )
44
m_collectionFetchStrategy( EntityTreeModel::FetchCollectionsRecursive ),
45
m_itemPopulation( EntityTreeModel::ImmediatePopulation ),
46
m_showRootCollection( false )
51
int EntityTreeModelPrivate::indexOf( const QList<Node*> &nodes, Entity::Id id ) const
54
foreach ( const Node *node, nodes ) {
63
void EntityTreeModelPrivate::fetchItems( const Collection &parent )
65
Q_Q( EntityTreeModel );
66
// kDebug() << parent.remoteId();
67
Akonadi::ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( parent, m_session );
68
itemJob->setFetchScope( m_monitor->itemFetchScope() );
70
// ### HACK: itemsReceivedFromJob needs to know which collection items were added to.
71
// That is not provided by akonadi, so we attach it in a property.
72
itemJob->setProperty( ItemFetchCollectionId(), QVariant( parent.id() ) );
74
q->connect( itemJob, SIGNAL( itemsReceived( const Akonadi::Item::List& ) ),
75
q, SLOT( itemsFetched( const Akonadi::Item::List& ) ) );
76
q->connect( itemJob, SIGNAL( result( KJob* ) ),
77
q, SLOT( fetchJobDone( KJob* ) ) );
80
void EntityTreeModelPrivate::fetchCollections( const Collection &collection, CollectionFetchJob::Type type )
82
Q_Q( EntityTreeModel );
83
CollectionFetchJob *job = new CollectionFetchJob( collection, type, m_session );
84
job->includeUnsubscribed( m_includeUnsubscribed );
85
q->connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ),
86
q, SLOT( collectionsFetched( const Akonadi::Collection::List& ) ) );
87
q->connect( job, SIGNAL( result( KJob* ) ),
88
q, SLOT( fetchJobDone( KJob* ) ) );
91
void EntityTreeModelPrivate::collectionsFetched( const Akonadi::Collection::List& collections )
93
// TODO: refactor this stuff into separate methods for listing resources in Collection::root, and listing collections within resources.
94
Q_Q( EntityTreeModel );
95
QHash<Collection::Id, Collection> newCollections;
96
QHash<Collection::Id, QList<Collection::Id> > newChildCollections;
98
Akonadi::AgentManager *agentManager = Akonadi::AgentManager::self();
100
foreach ( const Collection collection, collections ) {
101
if ( m_collections.contains( collection.id() ) ) {
102
// If we already know about the collection, there is nothing left to do
105
// ... otherwise we add it to the set of collections we need to handle.
106
if ( collection.parent() == Collection::root().id() ) {
107
Akonadi::AgentInstance agentInstance = agentManager->instance( collection.resource() );
109
if ( ( !m_mimeChecker.isWantedCollection( collection ) ) &&
110
( !m_monitor->resourcesMonitored().contains( collection.resource().toUtf8() ) ) )
114
newChildCollections[ collection.parent() ].append( collection.id() );
115
newCollections.insert( collection.id(), collection );
118
// Insert new child collections into model.
119
QHashIterator<Collection::Id, QList<Collection::Id> > it( newChildCollections );
120
while ( it.hasNext() ) {
123
// the key is the parent of new collections.
124
const Collection::Id parentId = it.key();
126
const QList<Collection::Id> newChildCollections = it.value();
127
const int newChildCount = newChildCollections.size();
129
if ( m_collections.contains( parentId ) ) {
130
int startRow = 0; // Prepend collections.
132
// TODO: account for ordering.
133
const QModelIndex parentIndex = q->indexForCollection( m_collections.value( parentId ) );
135
q->beginInsertRows( parentIndex, startRow, startRow + newChildCount - 1 );
136
foreach ( const Collection::Id id, newChildCollections ) {
137
const Collection collection = newCollections.value( id );
138
m_collections.insert( id, collection );
140
Node *node = new Node;
142
node->parent = parentId;
143
node->type = Node::Collection;
144
m_childEntities[ parentId ].prepend( node );
148
foreach ( const Collection::Id id, newChildCollections ) {
149
const Collection collection = newCollections.value( id );
151
// Fetch the next level of collections if necessary.
152
if ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive )
153
fetchCollections( collection, CollectionFetchJob::FirstLevel );
155
// Fetch items if necessary. If we don't fetch them now, we'll wait for an application
156
// to request them through EntityTreeModel::fetchMore
157
if ( m_itemPopulation == EntityTreeModel::ImmediatePopulation )
158
fetchItems( collection );
161
// TODO: Fetch parent again so that its entities get ordered properly. Or start a modify job?
162
// Should I do this for all other cases as well instead of using transactions?
163
// Could be a way around the fact that monitor could notify me of things out of order. a parent could
164
// be 'changed' for its entities to be reordered before one of its entities is in the model yet.
168
void EntityTreeModelPrivate::itemsFetched( const Akonadi::Item::List& items )
170
Q_Q( EntityTreeModel );
171
QObject *job = q->sender();
173
const Collection::Id collectionId = job->property( ItemFetchCollectionId() ).value<Collection::Id>();
174
Item::List itemsToInsert;
175
Item::List itemsToUpdate;
177
const Collection collection = m_collections.value( collectionId );
179
const QList<Node*> collectionEntities = m_childEntities.value( collectionId );
180
foreach ( const Item &item, items ) {
181
if ( indexOf( collectionEntities, item.id() ) != -1 ) {
182
itemsToUpdate << item;
184
if ( m_mimeChecker.isWantedItem( item ) ) {
185
itemsToInsert << item;
189
if ( itemsToInsert.size() > 0 ) {
190
const int startRow = m_childEntities.value( collectionId ).size();
192
const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collectionId ) );
193
q->beginInsertRows( parentIndex, startRow, startRow + items.size() - 1 );
194
foreach ( const Item &item, items ) {
195
Item::Id itemId = item.id();
196
m_items.insert( itemId, item );
198
Node *node = new Node;
200
node->parent = collectionId;
201
node->type = Node::Item;
203
m_childEntities[ collectionId ].append( node );
210
void EntityTreeModelPrivate::monitoredMimeTypeChanged( const QString & mimeType, bool monitored )
213
m_mimeChecker.addWantedMimeType( mimeType );
215
m_mimeChecker.removeWantedMimeType( mimeType );
218
void EntityTreeModelPrivate::monitoredCollectionAdded( const Akonadi::Collection& collection, const Akonadi::Collection& parent )
220
Q_Q( EntityTreeModel );
221
// if ( !passesFilter( collection.contentMimeTypes() ) )
224
// TODO: Use order attribute of parent if available
225
// Otherwise prepend collections and append items. Currently this prepends all collections.
227
// Or I can prepend and append for single signals, then 'change' the parent.
229
// QList<qint64> childCols = m_childEntities.value( parent.id() );
230
// int row = childCols.size();
231
// int numChildCols = childCollections.value(parent.id()).size();
235
const QModelIndex parentIndex = q->indexForCollection( parent );
236
q->beginInsertRows( parentIndex, row, row );
237
m_collections.insert( collection.id(), collection );
239
Node *node = new Node;
240
node->id = collection.id();
241
node->parent = parent.id();
242
node->type = Node::Collection;
243
m_childEntities[ parent.id() ].prepend( node );
247
void EntityTreeModelPrivate::monitoredCollectionRemoved( const Akonadi::Collection& collection )
249
Q_Q( EntityTreeModel );
251
const int row = indexOf( m_childEntities.value( collection.parent() ), collection.id() );
253
// int row = m_childEntities.value(collection.parent()).indexOf(collection.id());
254
Q_ASSERT( row >= 0 );
255
const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.parent() ) );
257
q->beginRemoveRows( parentIndex, row, row );
259
// TODO: Also need to handle all descendant collections and items here.
261
// Remove deleted collection.
262
m_collections.remove( collection.id() );
264
// Remove children of deleted collection.
265
m_childEntities.remove( collection.id() );
267
// Remove deleted collection from its parent.
268
m_childEntities[ collection.parent() ].removeAt( row );
273
void EntityTreeModelPrivate::monitoredCollectionMoved( const Akonadi::Collection& collection,
274
const Akonadi::Collection& sourceCollection,
275
const Akonadi::Collection& destCollection )
277
Q_Q( EntityTreeModel );
279
const int srcRow = indexOf( m_childEntities.value( sourceCollection.id() ), collection.id() );
281
const QModelIndex srcParentIndex = q->indexForCollection( sourceCollection );
282
const QModelIndex destParentIndex = q->indexForCollection( destCollection );
284
const int destRow = 0; // Prepend collections
286
q->beginMoveRows( srcParentIndex, srcRow, srcRow, destParentIndex, destRow );
287
Node *node = m_childEntities[ sourceCollection.id() ].takeAt( srcRow );
288
m_childEntities[ destCollection.id() ].prepend( node );
292
void EntityTreeModelPrivate::monitoredCollectionChanged( const Akonadi::Collection &collection )
294
Q_Q( EntityTreeModel );
296
if ( m_collections.contains( collection.id() ) )
297
m_collections[ collection.id() ] = collection;
299
const QModelIndex index = q->indexForCollection( collection );
300
q->dataChanged( index, index );
303
void EntityTreeModelPrivate::monitoredCollectionStatisticsChanged( Akonadi::Collection::Id,
304
const Akonadi::CollectionStatistics& )
309
void EntityTreeModelPrivate::monitoredItemAdded( const Akonadi::Item& item, const Akonadi::Collection& collection )
311
Q_Q( EntityTreeModel );
313
if ( !m_mimeChecker.isWantedItem( item ) )
316
const int row = m_childEntities.value( collection.id() ).size();
318
const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) );
320
q->beginInsertRows( parentIndex, row, row );
321
m_items.insert( item.id(), item );
322
Node *node = new Node;
323
node->id = item.id();
324
node->parent = collection.id();
325
node->type = Node::Item;
326
m_childEntities[ collection.id() ].append( node );
330
void EntityTreeModelPrivate::monitoredItemRemoved( const Akonadi::Item &item )
332
Q_Q( EntityTreeModel );
334
const Collection collection = getParentCollections( item ).at( 0 );
336
const int row = indexOf( m_childEntities.value( collection.id() ), item.id() );
338
const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) );
340
q->beginInsertRows( parentIndex, row, row );
341
m_items.remove( item.id() );
342
m_childEntities[ collection.id() ].removeAt( row );
346
void EntityTreeModelPrivate::monitoredItemChanged( const Akonadi::Item &item, const QSet<QByteArray>& )
348
Q_Q( EntityTreeModel );
349
m_items[ item.id() ] = item;
351
const QModelIndexList indexes = q->indexesForItem( item );
352
foreach ( const QModelIndex &index, indexes )
353
q->dataChanged( index, index );
356
void EntityTreeModelPrivate::monitoredItemMoved( const Akonadi::Item& item,
357
const Akonadi::Collection& sourceItem,
358
const Akonadi::Collection& destItem )
360
Q_Q( EntityTreeModel );
362
const Item::Id itemId = item.id();
364
const int srcRow = indexOf( m_childEntities.value( sourceItem.id() ), itemId );
366
const QModelIndex srcIndex = q->indexForCollection( sourceItem );
367
const QModelIndex destIndex = q->indexForCollection( destItem );
369
// Where should it go? Always append items and prepend collections and reorganize them with separate reactions to Attributes?
371
const int destRow = q->rowCount( destIndex );
373
q->beginMoveRows( srcIndex, srcRow, srcRow, destIndex, destRow );
374
Node *node = m_childEntities[ sourceItem.id() ].takeAt( srcRow );
375
m_childEntities[ destItem.id() ].append( node );
379
void EntityTreeModelPrivate::monitoredItemLinked( const Akonadi::Item& item, const Akonadi::Collection& collection )
381
kDebug() << item.remoteId() << collection.id();
382
Q_Q( EntityTreeModel );
384
if ( !m_mimeChecker.isWantedItem( item ) )
387
const int row = m_childEntities.value( collection.id() ).size();
389
const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) );
391
q->beginInsertRows( parentIndex, row, row );
392
Node *node = new Node;
393
node->id = item.id();
394
node->parent = collection.id();
395
node->type = Node::Item;
396
m_childEntities[ collection.id()].append( node );
400
void EntityTreeModelPrivate::monitoredItemUnlinked( const Akonadi::Item& item, const Akonadi::Collection& collection )
402
Q_Q( EntityTreeModel );
404
const int row = indexOf( m_childEntities.value( collection.id() ), item.id() );
406
const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) );
408
q->beginInsertRows( parentIndex, row, row );
409
m_childEntities[ collection.id() ].removeAt( row );
413
void EntityTreeModelPrivate::fetchJobDone( KJob *job )
415
if ( job->error() ) {
416
kWarning( 5250 ) << "Job error: " << job->errorString() << endl;
420
void EntityTreeModelPrivate::copyJobDone( KJob *job )
422
if ( job->error() ) {
423
kWarning( 5250 ) << "Job error: " << job->errorString() << endl;
427
void EntityTreeModelPrivate::moveJobDone( KJob *job )
429
if ( job->error() ) {
430
kWarning( 5250 ) << "Job error: " << job->errorString() << endl;
434
void EntityTreeModelPrivate::updateJobDone( KJob *job )
436
if ( job->error() ) {
437
// TODO: handle job errors
438
kWarning( 5250 ) << "Job error:" << job->errorString();
440
// TODO: Is this trying to do the job of collectionstatisticschanged?
441
// CollectionStatisticsJob *csjob = static_cast<CollectionStatisticsJob*>( job );
442
// Collection result = csjob->collection();
443
// collectionStatisticsChanged( result.id(), csjob->statistics() );
447
void EntityTreeModelPrivate::startFirstListJob()
449
Q_Q(EntityTreeModel);
451
kDebug() << m_collections.size();
453
if (m_collections.size() > 0)
456
Collection rootCollection;
457
// Even if the root collection is the invalid collection, we still need to start
458
// the first list job with Collection::root.
459
if ( m_showRootCollection ) {
460
rootCollection = Collection::root();
461
// Notify the outside that we're putting collection::root into the model.
462
// kDebug() << "begin";
463
q->beginInsertRows( QModelIndex(), 0, 0 );
464
m_collections.insert( rootCollection.id(), rootCollection );
465
m_rootNode = new Node;
466
m_rootNode->id = rootCollection.id();
467
m_rootNode->parent = -1;
468
m_rootNode->type = Node::Collection;
469
m_childEntities[ -1 ].append( m_rootNode );
470
// kDebug() << "why";
473
// Otherwise store it silently because it's not part of the usable model.
474
rootCollection = m_rootCollection;
475
m_rootNode = new Node;
476
m_rootNode->id = rootCollection.id();
477
m_rootNode->parent = -1;
478
m_rootNode->type = Node::Collection;
479
m_collections.insert( rootCollection.id(), rootCollection );
482
// kDebug() << "inserting" << rootCollection.id();
484
// Includes recursive trees. Lower levels are fetched in the onRowsInserted slot if
486
if ( ( m_collectionFetchStrategy == EntityTreeModel::FetchFirstLevelChildCollections)
487
|| ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive ) ) {
488
fetchCollections( rootCollection, CollectionFetchJob::FirstLevel );
490
// If the root collection is not collection::root, then it could have items, and they will need to be
493
if ( m_itemPopulation != EntityTreeModel::NoItemPopulation ) {
494
// kDebug() << (rootCollection == Collection::root());
495
if (rootCollection != Collection::root())
496
fetchItems( rootCollection );
500
Collection EntityTreeModelPrivate::getParentCollection( Entity::Id id ) const
502
QHashIterator<Collection::Id, QList<Node*> > iter( m_childEntities );
503
while ( iter.hasNext() ) {
505
if ( indexOf( iter.value(), id ) != -1 ) {
506
return m_collections.value( iter.key() );
513
Collection::List EntityTreeModelPrivate::getParentCollections( const Item &item ) const
515
Collection::List list;
516
QHashIterator<Collection::Id, QList<Node*> > iter( m_childEntities );
517
while ( iter.hasNext() ) {
519
if ( indexOf( iter.value(), item.id() ) != -1 ) {
520
list << m_collections.value( iter.key() );
527
Collection EntityTreeModelPrivate::getParentCollection( const Collection &collection ) const
529
return m_collections.value( collection.parent() );
532
Entity::Id EntityTreeModelPrivate::childAt( Collection::Id id, int position, bool *ok ) const
534
const QList<Node*> list = m_childEntities.value( id );
535
if ( list.size() <= position ) {
541
return list.at( position )->id;
545
int EntityTreeModelPrivate::indexOf( Collection::Id parent, Collection::Id collectionId ) const
547
return indexOf( m_childEntities.value( parent ), collectionId );
550
Item EntityTreeModelPrivate::getItem( Item::Id id) const
555
return m_items.value( id );