diff --git a/akonadi/itemmodel.cpp b/akonadi/itemmodel.cpp index d76b1324e..07ff1e83b 100644 --- a/akonadi/itemmodel.cpp +++ b/akonadi/itemmodel.cpp @@ -1,403 +1,427 @@ /* Copyright (c) 2006 - 2007 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. */ #include "itemmodel.h" #include "itemfetchjob.h" +#include "collectionfetchjob.h" #include "itemfetchscope.h" #include "monitor.h" #include "pastehelper_p.h" #include "session.h" #include #include #include #include #include #include #include using namespace Akonadi; /** * @internal * * This struct is used for optimization reasons. * because it embeds the row. * * Semantically, we could have used an item instead. */ struct ItemContainer { ItemContainer( const Item& i, int r ) { item = i; row = r; } Item item; int row; }; /** * @internal */ class ItemModel::Private { public: Private( ItemModel *parent ) : mParent( parent ), monitor( new Monitor() ) { session = new Session( QCoreApplication::instance()->applicationName().toUtf8() + QByteArray("-ItemModel-") + QByteArray::number( qrand() ), mParent ); monitor->ignoreSession( session ); mParent->connect( monitor, SIGNAL(itemChanged( const Akonadi::Item&, const QSet& )), mParent, SLOT(itemChanged( const Akonadi::Item&, const QSet& )) ); mParent->connect( monitor, SIGNAL(itemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& )), mParent, SLOT(itemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ) ) ); mParent->connect( monitor, SIGNAL(itemAdded( const Akonadi::Item&, const Akonadi::Collection& )), mParent, SLOT(itemAdded( const Akonadi::Item& )) ); mParent->connect( monitor, SIGNAL(itemRemoved(Akonadi::Item)), mParent, SLOT(itemRemoved(Akonadi::Item)) ); mParent->connect( monitor, SIGNAL(itemLinked(const Akonadi::Item&, const Akonadi::Collection&)), mParent, SLOT(itemAdded(const Akonadi::Item&)) ); mParent->connect( monitor, SIGNAL(itemUnlinked(const Akonadi::Item&, const Akonadi::Collection&)), mParent, SLOT(itemRemoved(const Akonadi::Item&)) ); } ~Private() { delete monitor; } void listingDone( KJob* ); + void collectionFetchResult( KJob* ); void itemChanged( const Akonadi::Item&, const QSet& ); void itemsAdded( const Akonadi::Item::List &list ); void itemAdded( const Akonadi::Item &item ); void itemMoved( const Akonadi::Item&, const Akonadi::Collection& src, const Akonadi::Collection& dst ); void itemRemoved( const Akonadi::Item& ); int rowForItem( const Akonadi::Item& ); ItemModel *mParent; QList items; QHash itemHash; Collection collection; Monitor *monitor; Session *session; }; void ItemModel::Private::listingDone( KJob * job ) { ItemFetchJob *fetch = static_cast( job ); Q_UNUSED( fetch ); if ( job->error() ) { // TODO kWarning( 5250 ) << "Item query failed:" << job->errorString(); } } +void ItemModel::Private::collectionFetchResult( KJob * job ) +{ + CollectionFetchJob *fetch = static_cast( job ); + Q_ASSERT( fetch->collections().count() == 1 ); // we only listed base + Collection c = fetch->collections().first(); + // avoid recursion, if this fails for some reason + if ( !c.contentMimeTypes().isEmpty() ) { + mParent->setCollection(c); + } else { + kWarning( 5250 ) << "Failed to retrieve the contents mime type of the collection: " << c; + mParent->setCollection(Collection()); + } +} + int ItemModel::Private::rowForItem( const Akonadi::Item& item ) { ItemContainer *container = itemHash.value( item ); if ( !container ) return -1; /* Try to find the item directly; If items have been removed, this first try won't succeed because the ItemContainer rows have not been updated (costs too much). */ if ( container->row < items.count() && items.at( container->row ) == container ) return container->row; else { // Slow solution if the fist one has not succeeded int row = -1; for ( int i = 0; i < items.size(); ++i ) { if ( items.at( i )->item == item ) { row = i; break; } } return row; } } void ItemModel::Private::itemChanged( const Akonadi::Item &item, const QSet& ) { int row = rowForItem( item ); if ( row < 0 ) return; items[ row ]->item = item; itemHash.remove( item ); itemHash[ item ] = items[ row ]; QModelIndex start = mParent->index( row, 0, QModelIndex() ); QModelIndex end = mParent->index( row, mParent->columnCount( QModelIndex() ) - 1 , QModelIndex() ); mParent->dataChanged( start, end ); } void ItemModel::Private::itemMoved( const Akonadi::Item &item, const Akonadi::Collection& colSrc, const Akonadi::Collection& colDst ) { if ( colSrc == collection && colDst != collection ) // item leaving this model { itemRemoved( item ); return; } if ( colDst == collection && colSrc != collection ) { itemAdded( item ); return; } } void ItemModel::Private::itemsAdded( const Akonadi::Item::List &list ) { if ( list.isEmpty() ) return; mParent->beginInsertRows( QModelIndex(), items.count(), items.count() + list.count() - 1 ); foreach( const Item &item, list ) { ItemContainer *c = new ItemContainer( item, items.count() ); items.append( c ); itemHash[ item ] = c; } mParent->endInsertRows(); } void ItemModel::Private::itemAdded( const Akonadi::Item &item ) { Item::List l; l << item; itemsAdded( l ); } void ItemModel::Private::itemRemoved( const Akonadi::Item &_item ) { int row = rowForItem( _item ); if ( row < 0 ) return; mParent->beginRemoveRows( QModelIndex(), row, row ); const Item item = items.at( row )->item; Q_ASSERT( item.isValid() ); itemHash.remove( item ); delete items.takeAt( row ); mParent->endRemoveRows(); } ItemModel::ItemModel( QObject *parent ) : QAbstractTableModel( parent ), d( new Private( this ) ) { setSupportedDragActions( Qt::MoveAction | Qt::CopyAction ); } ItemModel::~ItemModel() { delete d; } QVariant ItemModel::data( const QModelIndex & index, int role ) const { if ( !index.isValid() ) return QVariant(); if ( index.row() >= d->items.count() ) return QVariant(); const Item item = d->items.at( index.row() )->item; if ( !item.isValid() ) return QVariant(); if ( role == Qt::DisplayRole ) { switch ( index.column() ) { case Id: return QString::number( item.id() ); case RemoteId: return item.remoteId(); case MimeType: return item.mimeType(); default: return QVariant(); } } if ( role == IdRole ) return item.id(); if ( role == ItemRole ) { QVariant var; var.setValue( item ); return var; } if ( role == MimeTypeRole ) return item.mimeType(); return QVariant(); } int ItemModel::rowCount( const QModelIndex & parent ) const { if ( !parent.isValid() ) return d->items.count(); return 0; } int ItemModel::columnCount(const QModelIndex & parent) const { if ( !parent.isValid() ) return 3; // keep in sync with Column enum return 0; } QVariant ItemModel::headerData( int section, Qt::Orientation orientation, int role ) const { if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { switch ( section ) { case Id: return i18n( "Id" ); case RemoteId: return i18n( "Remote Id" ); case MimeType: return i18n( "MimeType" ); default: return QString(); } } return QAbstractTableModel::headerData( section, orientation, role ); } void ItemModel::setCollection( const Collection &collection ) { kDebug( 5250 ); if ( d->collection == collection ) return; + // if we don't know anything about this collection yet, fetch it + if ( collection.isValid() && collection.contentMimeTypes().isEmpty() ) + { + CollectionFetchJob* job = new CollectionFetchJob( collection, CollectionFetchJob::Base, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*)) ); + return; + } + d->monitor->setCollectionMonitored( d->collection, false ); d->collection = collection; d->monitor->setCollectionMonitored( d->collection, true ); // the query changed, thus everything we have already is invalid qDeleteAll( d->items ); d->items.clear(); reset(); // stop all running jobs d->session->clear(); // start listing job ItemFetchJob* job = new ItemFetchJob( collection, session() ); job->setFetchScope( d->monitor->itemFetchScope() ); connect( job, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(itemsAdded(Akonadi::Item::List)) ); connect( job, SIGNAL(result(KJob*)), SLOT(listingDone(KJob*)) ); emit collectionChanged( collection ); } void ItemModel::setFetchScope( const ItemFetchScope &fetchScope ) { d->monitor->setItemFetchScope( fetchScope ); } ItemFetchScope &ItemModel::fetchScope() { return d->monitor->itemFetchScope(); } Item ItemModel::itemForIndex( const QModelIndex & index ) const { if ( !index.isValid() ) return Akonadi::Item(); if ( index.row() >= d->items.count() ) return Akonadi::Item(); Item item = d->items.at( index.row() )->item; Q_ASSERT( item.isValid() ); return item; } Qt::ItemFlags ItemModel::flags( const QModelIndex &index ) const { Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index); if (index.isValid()) return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; else return Qt::ItemIsDropEnabled | defaultFlags; } QStringList ItemModel::mimeTypes() const { return QStringList(); } Session * ItemModel::session() const { return d->session; } QMimeData *ItemModel::mimeData( const QModelIndexList &indexes ) const { QMimeData *data = new QMimeData(); // Add item uri to the mimedata for dropping in external applications KUrl::List urls; foreach ( const QModelIndex &index, indexes ) { if ( index.column() != 0 ) continue; urls << itemForIndex( index ).url( Item::UrlWithMimeType ); } urls.populateMimeData( data ); return data; } QModelIndex ItemModel::indexForItem( const Akonadi::Item &item, const int column ) const { return index( d->rowForItem( item ), column ); } bool ItemModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) { Q_UNUSED( row ); Q_UNUSED( column ); Q_UNUSED( parent ); KJob* job = PasteHelper::paste( data, d->collection, action != Qt::MoveAction ); // TODO: error handling return job; } Collection ItemModel::collection() const { return d->collection; } #include "itemmodel.moc" diff --git a/akonadi/itemmodel.h b/akonadi/itemmodel.h index 048b99109..a897ee61a 100644 --- a/akonadi/itemmodel.h +++ b/akonadi/itemmodel.h @@ -1,191 +1,192 @@ /* Copyright (c) 2006 - 2007 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_ITEMMODEL_H #define AKONADI_ITEMMODEL_H #include "akonadi_export.h" #include #include #include namespace Akonadi { class Collection; class ItemFetchScope; class Job; class Session; /** * @short A table model for items. * * A self-updating table model that shows all items of * a collection. * * @code * * QTableView *view = new QTableView( this ); * * Akonadi::ItemModel *model = new Akonadi::ItemModel(); * view->setModel( model ); * * model->setCollection( Akonadi::Collection::root() ); * * @endcode * * @author Volker Krause */ class AKONADI_EXPORT ItemModel : public QAbstractTableModel { Q_OBJECT public: /** * Describes the types of the columns in the model. */ enum Column { Id = 0, ///< The unique id. RemoteId, ///< The remote identifier. MimeType ///< The item's mime type. }; /** * Describes the roles of the model. */ enum Roles { IdRole = Qt::UserRole + 1, ///< The id of the item. ItemRole, ///< The item object. MimeTypeRole, ///< The mime type of the item. UserRole = Qt::UserRole + 42 ///< Role for user extensions. }; /** * Creates a new item model. * * @param parent The parent object. */ explicit ItemModel( QObject* parent = 0 ); /** * Destroys the item model. */ virtual ~ItemModel(); virtual int columnCount( const QModelIndex & parent = QModelIndex() ) const; virtual QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; virtual int rowCount( const QModelIndex & parent = QModelIndex() ) const; virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; virtual Qt::ItemFlags flags( const QModelIndex &index ) const; virtual QMimeData *mimeData( const QModelIndexList &indexes ) const; virtual QStringList mimeTypes() const; /** * Sets the item fetch scope. * * The ItemFetchScope controls how much of an item's data is fetched from the * server, e.g. whether to fetch the full item payload or only meta data. * * @param fetchScope The new scope for item fetch operations. * * @see fetchScope() */ void setFetchScope( const ItemFetchScope &fetchScope ); /** * Returns the item fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the ItemFetchScope documentation * for an example. * * @return a reference to the current item fetch scope. * * @see setFetchScope() for replacing the current item fetch scope. */ ItemFetchScope &fetchScope(); /** * Returns the item at the given @p index. */ Item itemForIndex( const QModelIndex &index ) const; /** * Returns the model index for the given item, with the given column. * * @param item The item to find. * @param column The column for the returned index. */ QModelIndex indexForItem( const Akonadi::Item& item, const int column ) const; bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ); /** * Returns the collection being displayed in the model. */ Collection collection() const; public Q_SLOTS: /** * Sets the collection the model should display. If the collection has * changed, the model is reset and a new message listing is requested * from the Akonadi storage. * * @param collection The collection. */ void setCollection( const Akonadi::Collection &collection ); Q_SIGNALS: /** * This signal is emitted whenever setCollection is called. * * @param collection The new collection. */ void collectionChanged( const Akonadi::Collection &collection ); protected: /** * Returns the Session object used for all operations by this model. */ Session* session() const; private: //@cond PRIVATE class Private; Private* const d; Q_PRIVATE_SLOT( d, void listingDone( KJob* ) ) + Q_PRIVATE_SLOT( d, void collectionFetchResult( KJob* ) ) Q_PRIVATE_SLOT( d, void itemChanged( const Akonadi::Item&, const QSet& ) ) Q_PRIVATE_SLOT( d, void itemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d, void itemAdded( const Akonadi::Item& ) ) Q_PRIVATE_SLOT( d, void itemsAdded( const Akonadi::Item::List& ) ) Q_PRIVATE_SLOT( d, void itemRemoved( const Akonadi::Item& ) ) //@endcond }; } #endif