diff --git a/akonadi/collectionmodel.h b/akonadi/collectionmodel.h index ce1cc9fe8..56429f96c 100644 --- a/akonadi/collectionmodel.h +++ b/akonadi/collectionmodel.h @@ -1,139 +1,142 @@ /* Copyright (c) 2006 - 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_COLLECTIONMODEL_H #define AKONADI_COLLECTIONMODEL_H #include "akonadi_export.h" #include #include namespace Akonadi { class CollectionModelPrivate; /** * @short A model for collections. * * This class provides the interface of QAbstractItemModel for the * collection tree of the Akonadi storage. * * @code * * Akonadi::CollectionModel *model = new Akonadi::CollectionModel( this ); * * QTreeView *view = new QTreeView( this ); * view->setModel( model ); * * @endcode * * If you want to list only collections of a special mime type, use * CollectionFilterProxyModel on top of this model. * * @author Volker Krause */ class AKONADI_EXPORT CollectionModel : public QAbstractItemModel { Q_OBJECT public: /** * Describes the roles for collections. */ enum Roles { OldCollectionIdRole = Qt::UserRole + 1, ///< The collection identifier. For binary compatibility to <4.3 OldCollectionRole = Qt::UserRole + 2, ///< The actual collection object. For binary compatibility to <4.3 CollectionIdRole = Qt::UserRole + 10, ///< The collection identifier. CollectionRole = Qt::UserRole + 11, ///< The actual collection object. UserRole = Qt::UserRole + 42 ///< Role for user extensions. }; /** * Creates a new collection model. * * @param parent The parent object. */ explicit CollectionModel( QObject *parent = 0 ); /** * Destroys the collection model. */ virtual ~CollectionModel(); /** * Sets whether collection statistics information shall be provided * by the model. * * @see CollectionStatistics. */ void fetchCollectionStatistics( bool enable ); /** * Sets whether unsubscribed collections shall be listed in the model. */ void includeUnsubscribed( bool include = true ); virtual int columnCount( const QModelIndex & parent = QModelIndex() ) const; virtual QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; virtual QModelIndex index( int row, int column, const QModelIndex & parent = QModelIndex() ) const; virtual QModelIndex parent( const QModelIndex & index ) const; virtual int rowCount( const QModelIndex & parent = QModelIndex() ) const; virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; virtual bool setHeaderData( int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole ); virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ); virtual Qt::ItemFlags flags( const QModelIndex &index ) const; virtual Qt::DropActions supportedDropActions() const; virtual QMimeData *mimeData( const QModelIndexList &indexes ) const; virtual bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ); virtual QStringList mimeTypes() const; + Q_SIGNALS: + void listDone(); + protected: /** * Returns the collection for a given collection @p id. */ Collection collectionForId( Collection::Id id ) const; //@cond PRIVATE Akonadi::CollectionModelPrivate *d_ptr; explicit CollectionModel( CollectionModelPrivate *d, QObject *parent = 0 ); //@endcond private: Q_DECLARE_PRIVATE( CollectionModel ) //@cond PRIVATE Q_PRIVATE_SLOT( d_func(), void startFirstListJob() ) Q_PRIVATE_SLOT( d_func(), void collectionRemoved( const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void collectionChanged( const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void updateDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void collectionStatisticsChanged( Akonadi::Collection::Id, const Akonadi::CollectionStatistics& ) ) Q_PRIVATE_SLOT( d_func(), void listDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void editDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void dropResult( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void collectionsChanged( const Akonadi::Collection::List& ) ) //@endcond }; } #endif diff --git a/akonadi/collectionmodel_p.cpp b/akonadi/collectionmodel_p.cpp index d0b89fb07..008ac2b48 100644 --- a/akonadi/collectionmodel_p.cpp +++ b/akonadi/collectionmodel_p.cpp @@ -1,332 +1,334 @@ /* Copyright (c) 2006 - 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //@cond PRIVATE #include "collectionmodel_p.h" #include "collectionmodel.h" #include "collectionutils_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "monitor.h" #include "session.h" #include #include #include #include #include using namespace Akonadi; void CollectionModelPrivate::collectionRemoved( const Akonadi::Collection &collection ) { Q_Q( CollectionModel ); QModelIndex colIndex = indexForId( collection.id() ); if ( colIndex.isValid() ) { QModelIndex parentIndex = q->parent( colIndex ); // collection is still somewhere in the hierarchy removeRowFromModel( colIndex.row(), parentIndex ); } else { if ( collections.contains( collection.id() ) ) { // collection is orphan, ie. the parent has been removed already collections.remove( collection.id() ); childCollections.remove( collection.id() ); } } } void CollectionModelPrivate::collectionChanged( const Akonadi::Collection &collection ) { Q_Q( CollectionModel ); // What kind of change is it ? Collection::Id oldParentId = collections.value( collection.id() ).parent(); Collection::Id newParentId = collection.parent(); if ( newParentId != oldParentId && oldParentId >= 0 ) { // It's a move removeRowFromModel( indexForId( collections[ collection.id() ].id() ).row(), indexForId( oldParentId ) ); Collection newParent; if ( newParentId == Collection::root().id() ) newParent = Collection::root(); else newParent = collections.value( newParentId ); CollectionFetchJob *job = new CollectionFetchJob( newParent, CollectionFetchJob::Recursive, session ); job->includeUnsubscribed( unsubscribed ); job->includeStatistics( fetchStatistics ); q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List)) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( listDone( KJob* ) ) ); } else { // It's a simple change CollectionFetchJob *job = new CollectionFetchJob( collection, CollectionFetchJob::Base, session ); job->includeUnsubscribed( unsubscribed ); job->includeStatistics( fetchStatistics ); q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List)) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( listDone( KJob* ) ) ); } } void CollectionModelPrivate::updateDone( KJob *job ) { if ( job->error() ) { // TODO: handle job errors kWarning( 5250 ) << "Job error:" << job->errorString(); } else { CollectionStatisticsJob *csjob = static_cast( job ); Collection result = csjob->collection(); collectionStatisticsChanged( result.id(), csjob->statistics() ); } } void CollectionModelPrivate::collectionStatisticsChanged( Collection::Id collection, const Akonadi::CollectionStatistics &statistics ) { Q_Q( CollectionModel ); if ( !collections.contains( collection ) ) kWarning( 5250 ) << "Got statistics response for non-existing collection:" << collection; else { collections[ collection ].setStatistics( statistics ); Collection col = collections.value( collection ); QModelIndex startIndex = indexForId( col.id() ); QModelIndex endIndex = indexForId( col.id(), q->columnCount( q->parent( startIndex ) ) - 1 ); emit q->dataChanged( startIndex, endIndex ); } } void CollectionModelPrivate::listDone( KJob *job ) { + Q_Q( CollectionModel ); if ( job->error() ) { kWarning( 5250 ) << "Job error: " << job->errorString() << endl; } + emit q->listDone(); } void CollectionModelPrivate::editDone( KJob * job ) { if ( job->error() ) { kWarning( 5250 ) << "Edit failed: " << job->errorString(); } } void CollectionModelPrivate::dropResult(KJob * job) { if ( job->error() ) { kWarning( 5250 ) << "Paste failed:" << job->errorString(); // TODO: error handling } } void CollectionModelPrivate::collectionsChanged( const Collection::List &cols ) { Q_Q( CollectionModel ); foreach ( Collection col, cols ) { //krazy:exclude=foreach non-const is needed here if ( collections.contains( col.id() ) ) { // If the collection is already known to the model, we simply update it... col.setStatistics( collections.value( col.id() ).statistics() ); collections[ col.id() ] = col; QModelIndex startIndex = indexForId( col.id() ); QModelIndex endIndex = indexForId( col.id(), q->columnCount( q->parent( startIndex ) ) - 1 ); emit q->dataChanged( startIndex, endIndex ); continue; } // ... otherwise we add it to the set of collections we need to handle. m_newChildCollections[ col.parent() ].append( col.id() ); m_newCollections.insert( col.id(), col ); } // Handle the collections in m_newChildCollections. If the collections // parent is already in the model, the collection can be added to the model. // Otherwise it is persisted until it has a valid parent in the model. int currentSize = m_newChildCollections.size(); int lastSize = -1; while ( currentSize > 0 ) { lastSize = currentSize; QMutableHashIterator< Collection::Id, QList< Collection::Id > > i( m_newChildCollections ); while ( i.hasNext() ) { i.next(); // the key is the parent of new collections. It may itself also be new, // but that will be handled later. Collection::Id colId = i.key(); QList< Collection::Id > newChildCols = i.value(); int newChildCount = newChildCols.size(); // if ( newChildCount == 0 ) // { // // Sanity check. // kDebug() << "No new child collections have been added to the collection:" << colId; // i.remove(); // currentSize--; // break; // } if ( collections.contains( colId ) || colId == Collection::root().id() ) { QModelIndex parentIndex = indexForId( colId ); int currentChildCount = childCollections.value( colId ).size(); q->beginInsertRows( parentIndex, currentChildCount, // Start index is at the end of existing collections. currentChildCount + newChildCount - 1 ); // End index is the result of the insertion. foreach( Collection::Id id, newChildCols ) { Collection c = m_newCollections.take( id ); collections.insert( id, c ); } childCollections[ colId ] << newChildCols; q->endInsertRows(); i.remove(); currentSize--; break; } } // We iterated through once without adding any more collections to the model. if ( currentSize == lastSize ) { // The remaining collections in the list do not have a valid parent in the model yet. They // might arrive in the next batch from the monitor, so they're still in m_newCollections // and m_newChildCollections. kDebug() << "Some collections did not have a parent in the model yet!"; break; } } } QModelIndex CollectionModelPrivate::indexForId( Collection::Id id, int column ) const { Q_Q( const CollectionModel ); if ( !collections.contains( id ) ) return QModelIndex(); Collection::Id parentId = collections.value( id ).parent(); // check if parent still exist or if this is an orphan collection if ( parentId != Collection::root().id() && !collections.contains( parentId ) ) return QModelIndex(); QList list = childCollections.value( parentId ); int row = list.indexOf( id ); if ( row >= 0 ) return q->createIndex( row, column, reinterpret_cast( collections.value( list.at(row) ).id() ) ); return QModelIndex(); } bool CollectionModelPrivate::removeRowFromModel( int row, const QModelIndex & parent ) { Q_Q( CollectionModel ); QList list; Collection parentCol; if ( parent.isValid() ) { parentCol = collections.value( parent.internalId() ); Q_ASSERT( parentCol.id() == parent.internalId() ); list = childCollections.value( parentCol.id() ); } else { parentCol = Collection::root(); list = childCollections.value( Collection::root().id() ); } if ( row < 0 || row >= list.size() ) { kWarning( 5250 ) << "Index out of bounds:" << row <<" parent:" << parentCol.id(); return false; } q->beginRemoveRows( parent, row, row ); Collection::Id delColId = list.takeAt( row ); foreach( Collection::Id childColId, childCollections[ delColId ] ) collections.remove( childColId ); collections.remove( delColId ); childCollections.remove( delColId ); // remove children of deleted collection childCollections.insert( parentCol.id(), list ); // update children of parent q->endRemoveRows(); return true; } bool CollectionModelPrivate::supportsContentType(const QModelIndex & index, const QStringList & contentTypes) { if ( !index.isValid() ) return false; Collection col = collections.value( index.internalId() ); Q_ASSERT( col.isValid() ); QStringList ct = col.contentMimeTypes(); foreach ( const QString &a, ct ) { if ( contentTypes.contains( a ) ) return true; } return false; } void CollectionModelPrivate::init() { Q_Q( CollectionModel ); session = new Session( QCoreApplication::instance()->applicationName().toUtf8() + QByteArray("-CollectionModel-") + QByteArray::number( qrand() ), q ); QTimer::singleShot( 0, q, SLOT(startFirstListJob()) ); // monitor collection changes monitor = new Monitor(); monitor->setCollectionMonitored( Collection::root() ); monitor->fetchCollection( true ); // ### Hack to get the kmail resource folder icons KIconLoader::global()->addAppDir( QLatin1String( "kmail" ) ); KIconLoader::global()->addAppDir( QLatin1String( "kdepim" ) ); // monitor collection changes q->connect( monitor, SIGNAL(collectionChanged(const Akonadi::Collection&)), q, SLOT(collectionChanged(const Akonadi::Collection&)) ); q->connect( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), q, SLOT(collectionChanged(Akonadi::Collection)) ); q->connect( monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), q, SLOT(collectionRemoved(Akonadi::Collection)) ); q->connect( monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), q, SLOT(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)) ); } void CollectionModelPrivate::startFirstListJob() { Q_Q( CollectionModel ); // start a list job CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, session ); job->includeUnsubscribed( unsubscribed ); job->includeStatistics( fetchStatistics ); q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List)) ); q->connect( job, SIGNAL(result(KJob*)), q, SLOT(listDone(KJob*)) ); } //@endcond