akonadi
itemmodel.cpp
00001 /* 00002 Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 00018 */ 00019 00020 #include "itemmodel.h" 00021 00022 #include "itemfetchjob.h" 00023 #include "collectionfetchjob.h" 00024 #include "itemfetchscope.h" 00025 #include "monitor.h" 00026 #include "pastehelper_p.h" 00027 #include "session.h" 00028 00029 #include <kmime/kmime_message.h> 00030 00031 #include <kdebug.h> 00032 #include <klocale.h> 00033 #include <kurl.h> 00034 00035 #include <QCoreApplication> 00036 #include <QtCore/QDebug> 00037 #include <QtCore/QMimeData> 00038 00039 using namespace Akonadi; 00040 00049 struct ItemContainer 00050 { 00051 ItemContainer( const Item& i, int r ) 00052 : item( i ), row( r ) 00053 { 00054 } 00055 Item item; 00056 int row; 00057 }; 00058 00062 class ItemModel::Private 00063 { 00064 public: 00065 Private( ItemModel *parent ) 00066 : mParent( parent ), monitor( new Monitor() ) 00067 { 00068 session = new Session( QCoreApplication::instance()->applicationName().toUtf8() 00069 + QByteArray( "-ItemModel-" ) + QByteArray::number( qrand() ), mParent ); 00070 00071 monitor->ignoreSession( session ); 00072 00073 mParent->connect( monitor, SIGNAL( itemChanged( const Akonadi::Item&, const QSet<QByteArray>& ) ), 00074 mParent, SLOT( itemChanged( const Akonadi::Item&, const QSet<QByteArray>& ) ) ); 00075 mParent->connect( monitor, SIGNAL( itemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ) ), 00076 mParent, SLOT( itemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ) ) ); 00077 mParent->connect( monitor, SIGNAL( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ), 00078 mParent, SLOT( itemAdded( const Akonadi::Item& ) ) ); 00079 mParent->connect( monitor, SIGNAL( itemRemoved( const Akonadi::Item& ) ), 00080 mParent, SLOT( itemRemoved( const Akonadi::Item& ) ) ); 00081 mParent->connect( monitor, SIGNAL( itemLinked( const Akonadi::Item&, const Akonadi::Collection& ) ), 00082 mParent, SLOT( itemAdded( const Akonadi::Item& ) ) ); 00083 mParent->connect( monitor, SIGNAL( itemUnlinked( const Akonadi::Item&, const Akonadi::Collection& ) ), 00084 mParent, SLOT( itemRemoved( const Akonadi::Item& ) ) ); 00085 } 00086 00087 ~Private() 00088 { 00089 delete monitor; 00090 } 00091 00092 void listingDone( KJob* ); 00093 void collectionFetchResult( KJob* ); 00094 void itemChanged( const Akonadi::Item&, const QSet<QByteArray>& ); 00095 void itemsAdded( const Akonadi::Item::List &list ); 00096 void itemAdded( const Akonadi::Item &item ); 00097 void itemMoved( const Akonadi::Item&, const Akonadi::Collection& src, const Akonadi::Collection& dst ); 00098 void itemRemoved( const Akonadi::Item& ); 00099 int rowForItem( const Akonadi::Item& ); 00100 bool collectionIsCompatible() const; 00101 00102 ItemModel *mParent; 00103 00104 QList<ItemContainer*> items; 00105 QHash<Item, ItemContainer*> itemHash; 00106 00107 Collection collection; 00108 Monitor *monitor; 00109 Session *session; 00110 }; 00111 00112 bool ItemModel::Private::collectionIsCompatible() const 00113 { 00114 // in the generic case, we show any collection 00115 if ( mParent->mimeTypes() == QStringList( QLatin1String( "text/uri-list" ) ) ) 00116 return true; 00117 // if the model's mime types are more specific, limit to those 00118 // collections that have matching types 00119 Q_FOREACH( const QString &type, mParent->mimeTypes() ) { 00120 if ( collection.contentMimeTypes().contains( type ) ) { 00121 return true; 00122 } 00123 } 00124 return false; 00125 } 00126 00127 void ItemModel::Private::listingDone( KJob * job ) 00128 { 00129 ItemFetchJob *fetch = static_cast<ItemFetchJob*>( job ); 00130 Q_UNUSED( fetch ); 00131 if ( job->error() ) { 00132 // TODO 00133 kWarning() << "Item query failed:" << job->errorString(); 00134 } 00135 } 00136 00137 void ItemModel::Private::collectionFetchResult( KJob * job ) 00138 { 00139 CollectionFetchJob *fetch = static_cast<CollectionFetchJob*>( job ); 00140 00141 if ( fetch->collections().isEmpty() ) 00142 return; 00143 00144 Q_ASSERT( fetch->collections().count() == 1 ); // we only listed base 00145 Collection c = fetch->collections().first(); 00146 // avoid recursion, if this fails for some reason 00147 if ( !c.contentMimeTypes().isEmpty() ) { 00148 mParent->setCollection(c); 00149 } else { 00150 kWarning() << "Failed to retrieve the contents mime type of the collection: " << c; 00151 mParent->setCollection(Collection()); 00152 } 00153 } 00154 00155 int ItemModel::Private::rowForItem( const Akonadi::Item& item ) 00156 { 00157 ItemContainer *container = itemHash.value( item ); 00158 if ( !container ) 00159 return -1; 00160 00161 /* Try to find the item directly; 00162 00163 If items have been removed, this first try won't succeed because 00164 the ItemContainer rows have not been updated (costs too much). 00165 */ 00166 if ( container->row < items.count() 00167 && items.at( container->row ) == container ) 00168 return container->row; 00169 else { // Slow solution if the fist one has not succeeded 00170 int row = -1; 00171 for ( int i = 0; i < items.size(); ++i ) { 00172 if ( items.at( i )->item == item ) { 00173 row = i; 00174 break; 00175 } 00176 } 00177 return row; 00178 } 00179 00180 } 00181 00182 void ItemModel::Private::itemChanged( const Akonadi::Item &item, const QSet<QByteArray>& ) 00183 { 00184 int row = rowForItem( item ); 00185 if ( row < 0 ) 00186 return; 00187 00188 items[ row ]->item = item; 00189 itemHash.remove( item ); 00190 itemHash[ item ] = items[ row ]; 00191 00192 QModelIndex start = mParent->index( row, 0, QModelIndex() ); 00193 QModelIndex end = mParent->index( row, mParent->columnCount( QModelIndex() ) - 1 , QModelIndex() ); 00194 00195 mParent->dataChanged( start, end ); 00196 } 00197 00198 void ItemModel::Private::itemMoved( const Akonadi::Item &item, const Akonadi::Collection& colSrc, const Akonadi::Collection& colDst ) 00199 { 00200 if ( colSrc == collection && colDst != collection ) // item leaving this model 00201 { 00202 itemRemoved( item ); 00203 return; 00204 } 00205 00206 00207 if ( colDst == collection && colSrc != collection ) 00208 { 00209 itemAdded( item ); 00210 return; 00211 } 00212 } 00213 00214 void ItemModel::Private::itemsAdded( const Akonadi::Item::List &list ) 00215 { 00216 if ( list.isEmpty() ) 00217 return; 00218 mParent->beginInsertRows( QModelIndex(), items.count(), items.count() + list.count() - 1 ); 00219 foreach ( const Item &item, list ) { 00220 ItemContainer *c = new ItemContainer( item, items.count() ); 00221 items.append( c ); 00222 itemHash[ item ] = c; 00223 } 00224 mParent->endInsertRows(); 00225 } 00226 00227 void ItemModel::Private::itemAdded( const Akonadi::Item &item ) 00228 { 00229 Item::List l; 00230 l << item; 00231 itemsAdded( l ); 00232 } 00233 00234 void ItemModel::Private::itemRemoved( const Akonadi::Item &_item ) 00235 { 00236 int row = rowForItem( _item ); 00237 if ( row < 0 ) 00238 return; 00239 00240 mParent->beginRemoveRows( QModelIndex(), row, row ); 00241 const Item item = items.at( row )->item; 00242 Q_ASSERT( item.isValid() ); 00243 itemHash.remove( item ); 00244 delete items.takeAt( row ); 00245 mParent->endRemoveRows(); 00246 } 00247 00248 ItemModel::ItemModel( QObject *parent ) : 00249 QAbstractTableModel( parent ), 00250 d( new Private( this ) ) 00251 { 00252 } 00253 00254 ItemModel::~ItemModel() 00255 { 00256 delete d; 00257 } 00258 00259 QVariant ItemModel::data( const QModelIndex & index, int role ) const 00260 { 00261 if ( !index.isValid() ) 00262 return QVariant(); 00263 if ( index.row() >= d->items.count() ) 00264 return QVariant(); 00265 const Item item = d->items.at( index.row() )->item; 00266 if ( !item.isValid() ) 00267 return QVariant(); 00268 00269 if ( role == Qt::DisplayRole ) { 00270 switch ( index.column() ) { 00271 case Id: 00272 return QString::number( item.id() ); 00273 case RemoteId: 00274 return item.remoteId(); 00275 case MimeType: 00276 return item.mimeType(); 00277 default: 00278 return QVariant(); 00279 } 00280 } 00281 00282 if ( role == IdRole ) 00283 return item.id(); 00284 00285 if ( role == ItemRole ) { 00286 QVariant var; 00287 var.setValue( item ); 00288 return var; 00289 } 00290 00291 if ( role == MimeTypeRole ) 00292 return item.mimeType(); 00293 00294 return QVariant(); 00295 } 00296 00297 int ItemModel::rowCount( const QModelIndex & parent ) const 00298 { 00299 if ( !parent.isValid() ) 00300 return d->items.count(); 00301 return 0; 00302 } 00303 00304 int ItemModel::columnCount(const QModelIndex & parent) const 00305 { 00306 if ( !parent.isValid() ) 00307 return 3; // keep in sync with Column enum 00308 return 0; 00309 } 00310 00311 QVariant ItemModel::headerData( int section, Qt::Orientation orientation, int role ) const 00312 { 00313 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { 00314 switch ( section ) { 00315 case Id: 00316 return i18n( "Id" ); 00317 case RemoteId: 00318 return i18n( "Remote Id" ); 00319 case MimeType: 00320 return i18n( "MimeType" ); 00321 default: 00322 return QString(); 00323 } 00324 } 00325 return QAbstractTableModel::headerData( section, orientation, role ); 00326 } 00327 00328 void ItemModel::setCollection( const Collection &collection ) 00329 { 00330 kDebug(); 00331 if ( d->collection == collection ) 00332 return; 00333 00334 // if we don't know anything about this collection yet, fetch it 00335 if ( collection.isValid() && collection.contentMimeTypes().isEmpty() ) 00336 { 00337 CollectionFetchJob* job = new CollectionFetchJob( collection, CollectionFetchJob::Base, this ); 00338 connect( job, SIGNAL( result( KJob* ) ), this, SLOT( collectionFetchResult( KJob* ) ) ); 00339 return; 00340 } 00341 00342 d->monitor->setCollectionMonitored( d->collection, false ); 00343 00344 d->collection = collection; 00345 00346 d->monitor->setCollectionMonitored( d->collection, true ); 00347 00348 // the query changed, thus everything we have already is invalid 00349 qDeleteAll( d->items ); 00350 d->items.clear(); 00351 reset(); 00352 00353 // stop all running jobs 00354 d->session->clear(); 00355 00356 // start listing job 00357 if ( d->collectionIsCompatible() ) { 00358 ItemFetchJob* job = new ItemFetchJob( collection, session() ); 00359 job->setFetchScope( d->monitor->itemFetchScope() ); 00360 connect( job, SIGNAL( itemsReceived( const Akonadi::Item::List& ) ), 00361 SLOT( itemsAdded( const Akonadi::Item::List& ) ) ); 00362 connect( job, SIGNAL( result( KJob* ) ), SLOT( listingDone( KJob* ) ) ); 00363 } 00364 00365 emit collectionChanged( collection ); 00366 } 00367 00368 void ItemModel::setFetchScope( const ItemFetchScope &fetchScope ) 00369 { 00370 d->monitor->setItemFetchScope( fetchScope ); 00371 } 00372 00373 ItemFetchScope &ItemModel::fetchScope() 00374 { 00375 return d->monitor->itemFetchScope(); 00376 } 00377 00378 Item ItemModel::itemForIndex( const QModelIndex & index ) const 00379 { 00380 if ( !index.isValid() ) 00381 return Akonadi::Item(); 00382 00383 if ( index.row() >= d->items.count() ) 00384 return Akonadi::Item(); 00385 00386 Item item = d->items.at( index.row() )->item; 00387 Q_ASSERT( item.isValid() ); 00388 00389 return item; 00390 } 00391 00392 Qt::ItemFlags ItemModel::flags( const QModelIndex &index ) const 00393 { 00394 Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index); 00395 00396 if (index.isValid()) 00397 return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; 00398 else 00399 return Qt::ItemIsDropEnabled | defaultFlags; 00400 } 00401 00402 QStringList ItemModel::mimeTypes() const 00403 { 00404 return QStringList() << QLatin1String( "text/uri-list" ); 00405 } 00406 00407 Session * ItemModel::session() const 00408 { 00409 return d->session; 00410 } 00411 00412 QMimeData *ItemModel::mimeData( const QModelIndexList &indexes ) const 00413 { 00414 QMimeData *data = new QMimeData(); 00415 // Add item uri to the mimedata for dropping in external applications 00416 KUrl::List urls; 00417 foreach ( const QModelIndex &index, indexes ) { 00418 if ( index.column() != 0 ) 00419 continue; 00420 00421 urls << itemForIndex( index ).url( Item::UrlWithMimeType ); 00422 } 00423 urls.populateMimeData( data ); 00424 00425 return data; 00426 } 00427 00428 QModelIndex ItemModel::indexForItem( const Akonadi::Item &item, const int column ) const 00429 { 00430 return index( d->rowForItem( item ), column ); 00431 } 00432 00433 bool ItemModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) 00434 { 00435 Q_UNUSED( row ); 00436 Q_UNUSED( column ); 00437 Q_UNUSED( parent ); 00438 KJob* job = PasteHelper::paste( data, d->collection, action != Qt::MoveAction ); 00439 // TODO: error handling 00440 return job; 00441 } 00442 00443 Collection ItemModel::collection() const 00444 { 00445 return d->collection; 00446 } 00447 00448 Qt::DropActions ItemModel::supportedDropActions() const 00449 { 00450 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; 00451 } 00452 00453 00454 #include "itemmodel.moc"