akonadi
entitytreemodel.cpp
00001 /* 00002 Copyright (c) 2008 Stephen Kelly <steveire@gmail.com> 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 "entitytreemodel.h" 00021 #include "entitytreemodel_p.h" 00022 00023 #include "monitor_p.h" 00024 00025 #include <QtCore/QHash> 00026 #include <QtCore/QMimeData> 00027 #include <QtCore/QTimer> 00028 #include <QtGui/QAbstractProxyModel> 00029 #include <QtGui/QApplication> 00030 #include <QtGui/QPalette> 00031 00032 #include <KDE/KIcon> 00033 #include <KDE/KLocale> 00034 #include <KDE/KMessageBox> 00035 #include <KDE/KUrl> 00036 00037 #include <akonadi/attributefactory.h> 00038 #include <akonadi/changerecorder.h> 00039 #include <akonadi/collectionmodifyjob.h> 00040 #include <akonadi/entitydisplayattribute.h> 00041 #include <akonadi/transactionsequence.h> 00042 #include <akonadi/itemmodifyjob.h> 00043 #include <akonadi/session.h> 00044 #include "collectionfetchscope.h" 00045 00046 #include "collectionutils_p.h" 00047 00048 #include "kdebug.h" 00049 #include "pastehelper_p.h" 00050 00051 Q_DECLARE_METATYPE( QSet<QByteArray> ) 00052 00053 using namespace Akonadi; 00054 00055 EntityTreeModel::EntityTreeModel( ChangeRecorder *monitor, 00056 QObject *parent 00057 ) 00058 : QAbstractItemModel( parent ), 00059 d_ptr( new EntityTreeModelPrivate( this ) ) 00060 { 00061 Q_D( EntityTreeModel ); 00062 d->init( monitor ); 00063 } 00064 00065 EntityTreeModel::EntityTreeModel( ChangeRecorder *monitor, 00066 EntityTreeModelPrivate *d, 00067 QObject *parent ) 00068 : QAbstractItemModel( parent ), 00069 d_ptr( d ) 00070 { 00071 d->init(monitor ); 00072 } 00073 00074 EntityTreeModel::~EntityTreeModel() 00075 { 00076 Q_D( EntityTreeModel ); 00077 00078 foreach ( const QList<Node*> &list, d->m_childEntities ) 00079 qDeleteAll( list ); 00080 d->m_rootNode = 0; 00081 00082 delete d_ptr; 00083 } 00084 00085 bool EntityTreeModel::includeUnsubscribed() const 00086 { 00087 Q_D( const EntityTreeModel ); 00088 return d->m_includeUnsubscribed; 00089 } 00090 00091 void EntityTreeModel::setIncludeUnsubscribed( bool show ) 00092 { 00093 Q_D( EntityTreeModel ); 00094 d->beginResetModel(); 00095 d->m_includeUnsubscribed = show; 00096 d->m_monitor->setAllMonitored( show ); 00097 d->endResetModel(); 00098 } 00099 00100 00101 bool EntityTreeModel::systemEntitiesShown() const 00102 { 00103 Q_D( const EntityTreeModel ); 00104 return d->m_showSystemEntities; 00105 } 00106 00107 void EntityTreeModel::setShowSystemEntities( bool show ) 00108 { 00109 Q_D( EntityTreeModel ); 00110 d->m_showSystemEntities = show; 00111 } 00112 00113 void EntityTreeModel::clearAndReset() 00114 { 00115 Q_D( EntityTreeModel ); 00116 d->beginResetModel(); 00117 d->endResetModel(); 00118 } 00119 00120 int EntityTreeModel::columnCount( const QModelIndex & parent ) const 00121 { 00122 // TODO: Statistics? 00123 if ( parent.isValid() && parent.column() != 0 ) 00124 return 0; 00125 00126 return qMax( entityColumnCount( CollectionTreeHeaders ), entityColumnCount( ItemListHeaders ) ); 00127 } 00128 00129 00130 QVariant EntityTreeModel::entityData( const Item &item, int column, int role ) const 00131 { 00132 if ( column == 0 ) { 00133 switch ( role ) { 00134 case Qt::DisplayRole: 00135 case Qt::EditRole: 00136 if ( item.hasAttribute<EntityDisplayAttribute>() && 00137 !item.attribute<EntityDisplayAttribute>()->displayName().isEmpty() ) { 00138 return item.attribute<EntityDisplayAttribute>()->displayName(); 00139 } else { 00140 if (!item.remoteId().isEmpty()) 00141 return item.remoteId(); 00142 return QString(QLatin1String("<") + QString::number( item.id() ) + QLatin1String(">")); 00143 } 00144 break; 00145 case Qt::DecorationRole: 00146 if ( item.hasAttribute<EntityDisplayAttribute>() && 00147 !item.attribute<EntityDisplayAttribute>()->iconName().isEmpty() ) 00148 return item.attribute<EntityDisplayAttribute>()->icon(); 00149 break; 00150 default: 00151 break; 00152 } 00153 } 00154 00155 return QVariant(); 00156 } 00157 00158 QVariant EntityTreeModel::entityData( const Collection &collection, int column, int role ) const 00159 { 00160 Q_D( const EntityTreeModel ); 00161 00162 if ( column > 0 ) 00163 return QString(); 00164 00165 if ( collection == Collection::root() ) { 00166 // Only display the root collection. It may not be edited. 00167 if ( role == Qt::DisplayRole ) 00168 return d->m_rootCollectionDisplayName; 00169 00170 if ( role == Qt::EditRole ) 00171 return QVariant(); 00172 } 00173 00174 switch ( role ) { 00175 case Qt::DisplayRole: 00176 case Qt::EditRole: 00177 if ( column == 0 ) { 00178 if ( collection.hasAttribute<EntityDisplayAttribute>() && 00179 !collection.attribute<EntityDisplayAttribute>()->displayName().isEmpty() ) { 00180 return collection.attribute<EntityDisplayAttribute>()->displayName(); 00181 } 00182 if ( !collection.name().isEmpty() ) 00183 return collection.name(); 00184 return i18n( "Loading..." ); 00185 } 00186 break; 00187 case Qt::DecorationRole: 00188 if ( collection.hasAttribute<EntityDisplayAttribute>() && 00189 !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty() ) { 00190 return collection.attribute<EntityDisplayAttribute>()->icon(); 00191 } 00192 return KIcon( CollectionUtils::defaultIconName( collection ) ); 00193 default: 00194 break; 00195 } 00196 00197 return QVariant(); 00198 } 00199 00200 QVariant EntityTreeModel::data( const QModelIndex & index, int role ) const 00201 { 00202 Q_D( const EntityTreeModel ); 00203 if ( role == SessionRole ) 00204 return QVariant::fromValue( qobject_cast<QObject *>( d->m_session ) ); 00205 00206 // Ugly, but at least the API is clean. 00207 const HeaderGroup headerGroup = static_cast<HeaderGroup>( ( role / static_cast<int>( TerminalUserRole ) ) ); 00208 00209 role %= TerminalUserRole; 00210 if ( !index.isValid() ) { 00211 if ( ColumnCountRole != role ) 00212 return QVariant(); 00213 00214 return entityColumnCount( headerGroup ); 00215 } 00216 00217 if ( ColumnCountRole == role ) 00218 return entityColumnCount( headerGroup ); 00219 00220 const Node *node = reinterpret_cast<Node *>( index.internalPointer() ); 00221 00222 if ( ParentCollectionRole == role && d->m_collectionFetchStrategy != FetchNoCollections ) { 00223 const Collection parentCollection = d->m_collections.value( node->parent ); 00224 Q_ASSERT( parentCollection.isValid() ); 00225 00226 return QVariant::fromValue( parentCollection ); 00227 } 00228 00229 if ( Node::Collection == node->type ) { 00230 00231 const Collection collection = d->m_collections.value( node->id ); 00232 00233 if ( !collection.isValid() ) 00234 return QVariant(); 00235 00236 switch ( role ) { 00237 case MimeTypeRole: 00238 return collection.mimeType(); 00239 break; 00240 case RemoteIdRole: 00241 return collection.remoteId(); 00242 break; 00243 case CollectionIdRole: 00244 return collection.id(); 00245 break; 00246 case ItemIdRole: 00247 // QVariant().toInt() is 0, not -1, so we have to handle the ItemIdRole 00248 // and CollectionIdRole (below) specially 00249 return -1; 00250 break; 00251 case CollectionRole: 00252 return QVariant::fromValue( collection ); 00253 break; 00254 case EntityUrlRole: 00255 return collection.url().url(); 00256 break; 00257 case UnreadCountRole: 00258 { 00259 CollectionStatistics statistics = collection.statistics(); 00260 return statistics.unreadCount(); 00261 } 00262 case FetchStateRole: 00263 { 00264 return d->m_pendingCollectionRetrieveJobs.contains(collection.id()) ? FetchingState : IdleState; 00265 } 00266 case CollectionSyncProgressRole: 00267 { 00268 return d->m_collectionSyncProgress.value( collection.id() ); 00269 } 00270 case Qt::BackgroundRole: 00271 { 00272 if ( collection.hasAttribute<EntityDisplayAttribute>() ) 00273 { 00274 EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>(); 00275 QColor color = eda->backgroundColor(); 00276 if ( color.isValid() ) 00277 return color; 00278 } 00279 // fall through. 00280 } 00281 default: 00282 return entityData( collection, index.column(), role ); 00283 break; 00284 } 00285 00286 } else if ( Node::Item == node->type ) { 00287 const Item item = d->m_items.value( node->id ); 00288 if ( !item.isValid() ) 00289 return QVariant(); 00290 00291 switch ( role ) { 00292 case ParentCollectionRole: 00293 return QVariant::fromValue( item.parentCollection() ); 00294 case MimeTypeRole: 00295 return item.mimeType(); 00296 break; 00297 case RemoteIdRole: 00298 return item.remoteId(); 00299 break; 00300 case ItemRole: 00301 return QVariant::fromValue( item ); 00302 break; 00303 case ItemIdRole: 00304 return item.id(); 00305 break; 00306 case CollectionIdRole: 00307 return -1; 00308 break; 00309 case LoadedPartsRole: 00310 return QVariant::fromValue( item.loadedPayloadParts() ); 00311 break; 00312 case AvailablePartsRole: 00313 return QVariant::fromValue( item.availablePayloadParts() ); 00314 break; 00315 case EntityUrlRole: 00316 return item.url( Akonadi::Item::UrlWithMimeType ).url(); 00317 break; 00318 case Qt::BackgroundRole: 00319 { 00320 if ( item.hasAttribute<EntityDisplayAttribute>() ) 00321 { 00322 EntityDisplayAttribute *eda = item.attribute<EntityDisplayAttribute>(); 00323 QColor color = eda->backgroundColor(); 00324 if ( color.isValid() ) 00325 return color; 00326 } 00327 // fall through. 00328 } 00329 default: 00330 return entityData( item, index.column(), role ); 00331 break; 00332 } 00333 } 00334 00335 return QVariant(); 00336 } 00337 00338 00339 Qt::ItemFlags EntityTreeModel::flags( const QModelIndex & index ) const 00340 { 00341 Q_D( const EntityTreeModel ); 00342 // Pass modeltest. 00343 // http://labs.trolltech.com/forums/topic/79 00344 if ( !index.isValid() ) 00345 return 0; 00346 00347 Qt::ItemFlags flags = QAbstractItemModel::flags( index ); 00348 00349 const Node *node = reinterpret_cast<Node *>( index.internalPointer() ); 00350 00351 if ( Node::Collection == node->type ) { 00352 // cut out entities will be shown as inactive 00353 if ( d->m_pendingCutCollections.contains( node->id ) ) 00354 return Qt::ItemIsSelectable; 00355 00356 const Collection collection = d->m_collections.value( node->id ); 00357 if ( collection.isValid() ) { 00358 00359 if ( collection == Collection::root() ) { 00360 // Selectable and displayable only. 00361 return flags; 00362 } 00363 00364 const int rights = collection.rights(); 00365 00366 if ( rights & Collection::CanChangeCollection ) { 00367 if ( index.column() == 0 ) 00368 flags |= Qt::ItemIsEditable; 00369 // Changing the collection includes changing the metadata (child entityordering). 00370 // Need to allow this by drag and drop. 00371 flags |= Qt::ItemIsDropEnabled; 00372 } 00373 if ( rights & ( Collection::CanCreateCollection | Collection::CanCreateItem | Collection::CanLinkItem ) ) { 00374 // Can we drop new collections and items into this collection? 00375 flags |= Qt::ItemIsDropEnabled; 00376 } 00377 00378 // dragging is always possible, even for read-only objects, but they can only be copied, not moved. 00379 flags |= Qt::ItemIsDragEnabled; 00380 00381 } 00382 } else if ( Node::Item == node->type ) { 00383 if ( d->m_pendingCutItems.contains( node->id ) ) 00384 return Qt::ItemIsSelectable; 00385 00386 // Rights come from the parent collection. 00387 00388 Collection parentCollection; 00389 if ( !index.parent().isValid() ) { 00390 parentCollection = d->m_rootCollection; 00391 } else { 00392 const Node *parentNode = reinterpret_cast<Node *>( index.parent().internalPointer() ); 00393 00394 parentCollection = d->m_collections.value( parentNode->id ); 00395 } 00396 if ( parentCollection.isValid() ) { 00397 const int rights = parentCollection.rights(); 00398 00399 // Can't drop onto items. 00400 if ( rights & Collection::CanChangeItem && index.column() == 0 ) { 00401 flags = flags | Qt::ItemIsEditable; 00402 } 00403 // dragging is always possible, even for read-only objects, but they can only be copied, not moved. 00404 flags |= Qt::ItemIsDragEnabled; 00405 } 00406 } 00407 00408 return flags; 00409 } 00410 00411 Qt::DropActions EntityTreeModel::supportedDropActions() const 00412 { 00413 return (Qt::CopyAction | Qt::MoveAction | Qt::LinkAction); 00414 } 00415 00416 QStringList EntityTreeModel::mimeTypes() const 00417 { 00418 // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example. 00419 return QStringList() << QLatin1String( "text/uri-list" ); 00420 } 00421 00422 bool EntityTreeModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) 00423 { 00424 Q_UNUSED( row ); 00425 Q_UNUSED( column ); 00426 Q_D( EntityTreeModel ); 00427 00428 // Can't drop onto Collection::root. 00429 if ( !parent.isValid() ) 00430 return false; 00431 00432 // TODO Use action and collection rights and return false if necessary 00433 00434 // if row and column are -1, then the drop was on parent directly. 00435 // data should then be appended on the end of the items of the collections as appropriate. 00436 // That will mean begin insert rows etc. 00437 // Otherwise it was a sibling of the row^th item of parent. 00438 // Needs to be handled when ordering is accounted for. 00439 00440 // Handle dropping between items as well as on items. 00441 // if ( row != -1 && column != -1 ) 00442 // { 00443 // } 00444 00445 00446 if ( action == Qt::IgnoreAction ) 00447 return true; 00448 00449 // Shouldn't do this. Need to be able to drop vcards for example. 00450 // if ( !data->hasFormat( "text/uri-list" ) ) 00451 // return false; 00452 00453 Node *node = reinterpret_cast<Node *>( parent.internalId() ); 00454 00455 Q_ASSERT( node ); 00456 00457 if ( Node::Item == node->type ) { 00458 if ( !parent.parent().isValid() ) { 00459 // The drop is somehow on an item with no parent (shouldn't happen) 00460 // The drop should be considered handled anyway. 00461 kWarning() << "Dropped onto item with no parent collection"; 00462 return true; 00463 } 00464 00465 // A drop onto an item should be considered as a drop onto its parent collection 00466 node = reinterpret_cast<Node *>( parent.parent().internalId() ); 00467 } 00468 00469 if ( Node::Collection == node->type ) { 00470 const Collection destCollection = d->m_collections.value( node->id ); 00471 00472 // Applications can't create new collections in root. Only resources can. 00473 if ( destCollection == Collection::root() ) 00474 // Accept the event so that it doesn't propagate. 00475 return true; 00476 00477 if ( data->hasFormat( QLatin1String( "text/uri-list" ) ) ) { 00478 00479 MimeTypeChecker mimeChecker; 00480 mimeChecker.setWantedMimeTypes( destCollection.contentMimeTypes() ); 00481 00482 const KUrl::List urls = KUrl::List::fromMimeData( data ); 00483 foreach ( const KUrl &url, urls ) { 00484 const Collection collection = d->m_collections.value( Collection::fromUrl( url ).id() ); 00485 if ( collection.isValid() ) { 00486 if ( collection.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) { 00487 kDebug() << "Error: source and destination of move are the same."; 00488 return false; 00489 } 00490 00491 if ( !mimeChecker.isWantedCollection( collection ) ) { 00492 kDebug() << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes(); 00493 return false; 00494 } 00495 00496 if ( url.hasQueryItem( QLatin1String( "name" ) ) ) { 00497 const QString collectionName = url.queryItemValue( QLatin1String( "name" ) ); 00498 const QStringList collectionNames = d->childCollectionNames( destCollection ); 00499 00500 if ( collectionNames.contains( collectionName ) ) { 00501 KMessageBox::error( 0, i18n( "The target collection '%1' contains already\na collection with name '%2'.", 00502 destCollection.name(), collection.name() ) ); 00503 return false; 00504 } 00505 } 00506 } else { 00507 const Item item = d->m_items.value( Item::fromUrl( url ).id() ); 00508 if ( item.isValid() ) { 00509 if ( item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction ) { 00510 kDebug() << "Error: source and destination of move are the same."; 00511 return false; 00512 } 00513 00514 if ( !mimeChecker.isWantedItem( item ) ) { 00515 kDebug() << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType(); 00516 return false; 00517 } 00518 } 00519 } 00520 } 00521 00522 KJob *job = PasteHelper::pasteUriList( data, destCollection, action, d->m_session ); 00523 if ( !job ) 00524 return false; 00525 00526 connect( job, SIGNAL( result( KJob* ) ), SLOT( pasteJobDone( KJob* ) ) ); 00527 00528 // Accpet the event so that it doesn't propagate. 00529 return true; 00530 } else { 00531 // not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do 00532 // fromMimeData for them. Hmm, put it in the same transaction with the above? 00533 // TODO: This should be handled first, not last. 00534 } 00535 } 00536 00537 return false; 00538 } 00539 00540 QModelIndex EntityTreeModel::index( int row, int column, const QModelIndex & parent ) const 00541 { 00542 00543 Q_D( const EntityTreeModel ); 00544 00545 if ( parent.column() > 0 ) 00546 return QModelIndex(); 00547 00548 //TODO: don't use column count here? Use some d-> func. 00549 if ( column >= columnCount() || column < 0 ) 00550 return QModelIndex(); 00551 00552 QList<Node*> childEntities; 00553 00554 const Node *parentNode = reinterpret_cast<Node*>( parent.internalPointer() ); 00555 00556 if ( !parentNode || !parent.isValid() ) { 00557 if ( d->m_showRootCollection ) 00558 childEntities << d->m_childEntities.value( -1 ); 00559 else 00560 childEntities = d->m_childEntities.value( d->m_rootCollection.id() ); 00561 } else { 00562 if ( parentNode->id >= 0 ) 00563 childEntities = d->m_childEntities.value( parentNode->id ); 00564 } 00565 00566 const int size = childEntities.size(); 00567 if ( row < 0 || row >= size ) 00568 return QModelIndex(); 00569 00570 Node *node = childEntities.at( row ); 00571 00572 return createIndex( row, column, reinterpret_cast<void*>( node ) ); 00573 } 00574 00575 QModelIndex EntityTreeModel::parent( const QModelIndex & index ) const 00576 { 00577 Q_D( const EntityTreeModel ); 00578 00579 if ( !index.isValid() ) 00580 return QModelIndex(); 00581 00582 if ( d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections ) 00583 return QModelIndex(); 00584 00585 const Node *node = reinterpret_cast<Node*>( index.internalPointer() ); 00586 00587 if ( !node ) 00588 return QModelIndex(); 00589 00590 const Collection collection = d->m_collections.value( node->parent ); 00591 00592 if ( !collection.isValid() ) 00593 return QModelIndex(); 00594 00595 if ( collection.id() == d->m_rootCollection.id() ) { 00596 if ( !d->m_showRootCollection ) 00597 return QModelIndex(); 00598 else 00599 return createIndex( 0, 0, reinterpret_cast<void *>( d->m_rootNode ) ); 00600 } 00601 00602 Q_ASSERT( collection.parentCollection().isValid() ); 00603 const int row = d->indexOf<Node::Collection>( d->m_childEntities.value( collection.parentCollection().id() ), collection.id() ); 00604 00605 Q_ASSERT( row >= 0 ); 00606 Node *parentNode = d->m_childEntities.value( collection.parentCollection().id() ).at( row ); 00607 00608 return createIndex( row, 0, reinterpret_cast<void*>( parentNode ) ); 00609 } 00610 00611 int EntityTreeModel::rowCount( const QModelIndex & parent ) const 00612 { 00613 Q_D( const EntityTreeModel ); 00614 00615 if ( d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections ) { 00616 if ( parent.isValid() ) 00617 return 0; 00618 else 00619 return d->m_items.size(); 00620 } 00621 00622 if ( !parent.isValid() ) { 00623 // If we're showing the root collection then it will be the only child of the root. 00624 if ( d->m_showRootCollection ) 00625 return d->m_childEntities.value( -1 ).size(); 00626 return d->m_childEntities.value( d->m_rootCollection.id() ).size(); 00627 } 00628 00629 if ( parent.column() != 0 ) 00630 return 0; 00631 00632 const Node *node = reinterpret_cast<Node*>( parent.internalPointer() ); 00633 00634 if ( !node ) 00635 return 0; 00636 00637 if ( Node::Item == node->type ) 00638 return 0; 00639 00640 Q_ASSERT( parent.isValid() ); 00641 return d->m_childEntities.value( node->id ).size(); 00642 } 00643 00644 int EntityTreeModel::entityColumnCount( HeaderGroup headerGroup ) const 00645 { 00646 // Not needed in this model. 00647 Q_UNUSED( headerGroup ); 00648 00649 return 1; 00650 } 00651 00652 QVariant EntityTreeModel::entityHeaderData( int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup ) const 00653 { 00654 Q_D( const EntityTreeModel ); 00655 // Not needed in this model. 00656 Q_UNUSED( headerGroup ); 00657 00658 if ( section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole ) { 00659 if ( d->m_rootCollection == Collection::root() ) 00660 return i18nc( "@title:column Name of a thing", "Name" ); 00661 return d->m_rootCollection.name(); 00662 } 00663 00664 return QAbstractItemModel::headerData( section, orientation, role ); 00665 } 00666 00667 QVariant EntityTreeModel::headerData( int section, Qt::Orientation orientation, int role ) const 00668 { 00669 const HeaderGroup headerGroup = static_cast<HeaderGroup>( (role / static_cast<int>( TerminalUserRole ) ) ); 00670 00671 role %= TerminalUserRole; 00672 return entityHeaderData( section, orientation, role, headerGroup ); 00673 } 00674 00675 QMimeData *EntityTreeModel::mimeData( const QModelIndexList &indexes ) const 00676 { 00677 Q_D( const EntityTreeModel ); 00678 00679 QMimeData *data = new QMimeData(); 00680 KUrl::List urls; 00681 foreach ( const QModelIndex &index, indexes ) { 00682 if ( index.column() != 0 ) 00683 continue; 00684 00685 if ( !index.isValid() ) 00686 continue; 00687 00688 const Node *node = reinterpret_cast<Node*>( index.internalPointer() ); 00689 00690 if ( Node::Collection == node->type ) 00691 urls << d->m_collections.value( node->id ).url( Collection::UrlWithName ); 00692 else if ( Node::Item == node->type ) 00693 urls << d->m_items.value( node->id ).url( Item::UrlWithMimeType ); 00694 else // if that happens something went horrible wrong 00695 Q_ASSERT( false ); 00696 } 00697 00698 urls.populateMimeData( data ); 00699 00700 return data; 00701 } 00702 00703 // Always return false for actions which take place asyncronously, eg via a Job. 00704 bool EntityTreeModel::setData( const QModelIndex &index, const QVariant &value, int role ) 00705 { 00706 Q_D( EntityTreeModel ); 00707 00708 const Node *node = reinterpret_cast<Node*>( index.internalPointer() ); 00709 00710 if ( role == PendingCutRole ) { 00711 if ( index.isValid() && value.toBool() ) { 00712 if ( Node::Collection == node->type ) 00713 d->m_pendingCutCollections.append( node->id ); 00714 00715 if ( Node::Item == node->type ) 00716 d->m_pendingCutItems.append( node->id ); 00717 } else { 00718 d->m_pendingCutCollections.clear(); 00719 d->m_pendingCutItems.clear(); 00720 } 00721 return true; 00722 } 00723 00724 if ( index.isValid() && node->type == Node::Collection && (role == CollectionRefRole || role == CollectionDerefRole) ) { 00725 const Collection collection = index.data( CollectionRole ).value<Collection>(); 00726 Q_ASSERT( collection.isValid() ); 00727 00728 if ( role == CollectionDerefRole ) 00729 d->deref( collection.id() ); 00730 else if ( role == CollectionRefRole ) 00731 d->ref( collection.id() ); 00732 } 00733 00734 if ( index.column() == 0 && ( role & ( Qt::EditRole | ItemRole | CollectionRole ) ) ) { 00735 if ( Node::Collection == node->type ) { 00736 00737 Collection collection = d->m_collections.value( node->id ); 00738 00739 if ( !collection.isValid() || !value.isValid() ) 00740 return false; 00741 00742 if ( Qt::EditRole == role ) { 00743 collection.setName( value.toString() ); 00744 00745 if ( collection.hasAttribute<EntityDisplayAttribute>() ) { 00746 EntityDisplayAttribute *displayAttribute = collection.attribute<EntityDisplayAttribute>(); 00747 displayAttribute->setDisplayName( value.toString() ); 00748 } 00749 } 00750 00751 if ( Qt::BackgroundRole == role ) 00752 { 00753 QColor color = value.value<QColor>(); 00754 00755 if ( !color.isValid() ) 00756 return false; 00757 00758 EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>( Entity::AddIfMissing ); 00759 eda->setBackgroundColor( color ); 00760 } 00761 00762 if ( CollectionRole == role ) 00763 collection = value.value<Collection>(); 00764 00765 CollectionModifyJob *job = new CollectionModifyJob( collection, d->m_session ); 00766 connect( job, SIGNAL( result( KJob* ) ), 00767 SLOT( updateJobDone( KJob* ) ) ); 00768 00769 return false; 00770 } else if ( Node::Item == node->type ) { 00771 00772 Item item = d->m_items.value( node->id ); 00773 00774 if ( !item.isValid() || !value.isValid() ) 00775 return false; 00776 00777 if ( Qt::EditRole == role ) { 00778 if ( item.hasAttribute<EntityDisplayAttribute>() ) { 00779 EntityDisplayAttribute *displayAttribute = item.attribute<EntityDisplayAttribute>( Entity::AddIfMissing ); 00780 displayAttribute->setDisplayName( value.toString() ); 00781 } 00782 } 00783 00784 if ( Qt::BackgroundRole == role ) 00785 { 00786 QColor color = value.value<QColor>(); 00787 00788 if ( !color.isValid() ) 00789 return false; 00790 00791 EntityDisplayAttribute *eda = item.attribute<EntityDisplayAttribute>( Entity::AddIfMissing ); 00792 eda->setBackgroundColor( color ); 00793 } 00794 00795 if ( ItemRole == role ) 00796 { 00797 item = value.value<Item>(); 00798 Q_ASSERT( item.id() == node->id ); 00799 } 00800 00801 ItemModifyJob *itemModifyJob = new ItemModifyJob( item, d->m_session ); 00802 connect( itemModifyJob, SIGNAL( result( KJob* ) ), 00803 SLOT( updateJobDone( KJob* ) ) ); 00804 00805 return false; 00806 } 00807 } 00808 00809 return QAbstractItemModel::setData( index, value, role ); 00810 } 00811 00812 bool EntityTreeModel::canFetchMore( const QModelIndex & parent ) const 00813 { 00814 Q_UNUSED(parent) 00815 return false; 00816 } 00817 00818 void EntityTreeModel::fetchMore( const QModelIndex & parent ) 00819 { 00820 Q_D( EntityTreeModel ); 00821 00822 if ( !d->canFetchMore( parent ) ) 00823 return; 00824 00825 if ( d->m_collectionFetchStrategy == InvisibleCollectionFetch ) 00826 return; 00827 00828 if ( d->m_itemPopulation == ImmediatePopulation ) 00829 // Nothing to do. The items are already in the model. 00830 return; 00831 else if ( d->m_itemPopulation == LazyPopulation ) { 00832 const Collection collection = parent.data( CollectionRole ).value<Collection>(); 00833 00834 if ( !collection.isValid() ) 00835 return; 00836 00837 d->fetchItems( collection ); 00838 } 00839 } 00840 00841 bool EntityTreeModel::hasChildren( const QModelIndex &parent ) const 00842 { 00843 Q_D( const EntityTreeModel ); 00844 00845 if ( d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections ) 00846 return parent.isValid() ? false : !d->m_items.isEmpty(); 00847 00848 // TODO: Empty collections right now will return true and get a little + to expand. 00849 // There is probably no way to tell if a collection 00850 // has child items in akonadi without first attempting an itemFetchJob... 00851 // Figure out a way to fix this. (Statistics) 00852 return ((rowCount( parent ) > 0) || (canFetchMore( parent ) && d->m_itemPopulation == LazyPopulation)); 00853 } 00854 00855 bool EntityTreeModel::entityMatch( const Item &item, const QVariant &value, Qt::MatchFlags flags ) const 00856 { 00857 Q_UNUSED( item ); 00858 Q_UNUSED( value ); 00859 Q_UNUSED( flags ); 00860 return false; 00861 } 00862 00863 bool EntityTreeModel::entityMatch( const Collection &collection, const QVariant &value, Qt::MatchFlags flags ) const 00864 { 00865 Q_UNUSED( collection ); 00866 Q_UNUSED( value ); 00867 Q_UNUSED( flags ); 00868 return false; 00869 } 00870 00871 QModelIndexList EntityTreeModel::match( const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags ) const 00872 { 00873 Q_D( const EntityTreeModel ); 00874 00875 if ( role == CollectionIdRole || role == CollectionRole ) { 00876 Collection::Id id; 00877 if ( role == CollectionRole ) { 00878 const Collection collection = value.value<Collection>(); 00879 id = collection.id(); 00880 } else { 00881 id = value.toLongLong(); 00882 } 00883 00884 QModelIndexList list; 00885 00886 const Collection collection = d->m_collections.value( id ); 00887 00888 if ( !collection.isValid() ) 00889 return list; 00890 00891 const QModelIndex collectionIndex = d->indexForCollection( collection ); 00892 Q_ASSERT( collectionIndex.isValid() ); 00893 list << collectionIndex; 00894 00895 return list; 00896 } 00897 00898 if ( role == ItemIdRole || role == ItemRole ) { 00899 Item::Id id; 00900 if ( role == ItemRole ) { 00901 const Item item = value.value<Item>(); 00902 id = item.id(); 00903 } else { 00904 id = value.toLongLong(); 00905 } 00906 QModelIndexList list; 00907 00908 const Item item = d->m_items.value( id ); 00909 if ( !item.isValid() ) 00910 return list; 00911 00912 return d->indexesForItem( item ); 00913 } 00914 00915 if ( role == EntityUrlRole ) { 00916 const KUrl url( value.toString() ); 00917 const Item item = Item::fromUrl( url ); 00918 00919 if ( item.isValid() ) 00920 return d->indexesForItem( d->m_items.value( item.id() ) ); 00921 00922 const Collection collection = Collection::fromUrl( url ); 00923 QModelIndexList list; 00924 if ( collection.isValid() ) 00925 list << d->indexForCollection( collection ); 00926 00927 return list; 00928 } 00929 00930 if ( role != AmazingCompletionRole ) 00931 return QAbstractItemModel::match( start, role, value, hits, flags ); 00932 00933 // Try to match names, and email addresses. 00934 QModelIndexList list; 00935 00936 if ( role < 0 || !start.isValid() || !value.isValid() ) 00937 return list; 00938 00939 const int column = 0; 00940 int row = start.row(); 00941 const QModelIndex parentIndex = start.parent(); 00942 const int parentRowCount = rowCount( parentIndex ); 00943 00944 while ( row < parentRowCount && (hits == -1 || list.size() < hits) ) { 00945 const QModelIndex idx = index( row, column, parentIndex ); 00946 const Item item = idx.data( ItemRole ).value<Item>(); 00947 00948 if ( !item.isValid() ) { 00949 const Collection collection = idx.data( CollectionRole ).value<Collection>(); 00950 if ( !collection.isValid() ) 00951 continue; 00952 00953 if ( entityMatch( collection, value, flags ) ) 00954 list << idx; 00955 00956 } else { 00957 if ( entityMatch( item, value, flags ) ) 00958 list << idx; 00959 } 00960 00961 ++row; 00962 } 00963 00964 return list; 00965 } 00966 00967 bool EntityTreeModel::insertRows( int, int, const QModelIndex& ) 00968 { 00969 return false; 00970 } 00971 00972 bool EntityTreeModel::insertColumns( int, int, const QModelIndex& ) 00973 { 00974 return false; 00975 } 00976 00977 bool EntityTreeModel::removeRows( int, int, const QModelIndex& ) 00978 { 00979 return false; 00980 } 00981 00982 bool EntityTreeModel::removeColumns( int, int, const QModelIndex& ) 00983 { 00984 return false; 00985 } 00986 00987 void EntityTreeModel::setItemPopulationStrategy( ItemPopulationStrategy strategy ) 00988 { 00989 Q_D( EntityTreeModel ); 00990 d->beginResetModel(); 00991 d->m_itemPopulation = strategy; 00992 00993 if ( strategy == NoItemPopulation ) { 00994 disconnect( d->m_monitor, SIGNAL( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ), 00995 this, SLOT( monitoredItemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ) ); 00996 disconnect( d->m_monitor, SIGNAL( itemChanged( const Akonadi::Item&, const QSet<QByteArray>& ) ), 00997 this, SLOT( monitoredItemChanged( const Akonadi::Item&, const QSet<QByteArray>& ) ) ); 00998 disconnect( d->m_monitor, SIGNAL( itemRemoved( const Akonadi::Item& ) ), 00999 this, SLOT( monitoredItemRemoved( const Akonadi::Item& ) ) ); 01000 disconnect( d->m_monitor, SIGNAL( itemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ) ), 01001 this, SLOT( monitoredItemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ) ) ); 01002 01003 disconnect( d->m_monitor, SIGNAL( itemLinked( const Akonadi::Item&, const Akonadi::Collection& ) ), 01004 this, SLOT( monitoredItemLinked( const Akonadi::Item&, const Akonadi::Collection& ) ) ); 01005 disconnect( d->m_monitor, SIGNAL( itemUnlinked( const Akonadi::Item&, const Akonadi::Collection& ) ), 01006 this, SLOT( monitoredItemUnlinked( const Akonadi::Item&, const Akonadi::Collection& ) ) ); 01007 } 01008 01009 d->m_monitor->d_ptr->useRefCounting = (strategy == LazyPopulation); 01010 01011 d->endResetModel(); 01012 } 01013 01014 EntityTreeModel::ItemPopulationStrategy EntityTreeModel::itemPopulationStrategy() const 01015 { 01016 Q_D( const EntityTreeModel ); 01017 return d->m_itemPopulation; 01018 } 01019 01020 void EntityTreeModel::setIncludeRootCollection( bool include ) 01021 { 01022 Q_D( EntityTreeModel ); 01023 d->beginResetModel(); 01024 d->m_showRootCollection = include; 01025 d->endResetModel(); 01026 } 01027 01028 bool EntityTreeModel::includeRootCollection() const 01029 { 01030 Q_D( const EntityTreeModel ); 01031 return d->m_showRootCollection; 01032 } 01033 01034 void EntityTreeModel::setRootCollectionDisplayName( const QString &displayName ) 01035 { 01036 Q_D( EntityTreeModel ); 01037 d->m_rootCollectionDisplayName = displayName; 01038 01039 // TODO: Emit datachanged if it is being shown. 01040 } 01041 01042 QString EntityTreeModel::rootCollectionDisplayName() const 01043 { 01044 Q_D( const EntityTreeModel ); 01045 return d->m_rootCollectionDisplayName; 01046 } 01047 01048 void EntityTreeModel::setCollectionFetchStrategy( CollectionFetchStrategy strategy ) 01049 { 01050 Q_D( EntityTreeModel ); 01051 d->beginResetModel(); 01052 d->m_collectionFetchStrategy = strategy; 01053 01054 if ( strategy == FetchNoCollections || strategy == InvisibleCollectionFetch ) { 01055 disconnect( d->m_monitor, SIGNAL( collectionChanged( const Akonadi::Collection& ) ), 01056 this, SLOT( monitoredCollectionChanged( const Akonadi::Collection& ) ) ); 01057 disconnect( d->m_monitor, SIGNAL( collectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ) ), 01058 this, SLOT( monitoredCollectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ) ) ); 01059 disconnect( d->m_monitor, SIGNAL( collectionRemoved( const Akonadi::Collection& ) ), 01060 this, SLOT( monitoredCollectionRemoved( const Akonadi::Collection& ) ) ); 01061 disconnect( d->m_monitor, 01062 SIGNAL( collectionMoved( const Akonadi::Collection&, const Akonadi::Collection&, const Akonadi::Collection& ) ), 01063 this, SLOT( monitoredCollectionMoved( const Akonadi::Collection&, const Akonadi::Collection&, const Akonadi::Collection& ) ) ); 01064 d->m_monitor->fetchCollection( false ); 01065 } else 01066 d->m_monitor->fetchCollection( true ); 01067 01068 d->endResetModel(); 01069 } 01070 01071 EntityTreeModel::CollectionFetchStrategy EntityTreeModel::collectionFetchStrategy() const 01072 { 01073 Q_D( const EntityTreeModel ); 01074 return d->m_collectionFetchStrategy; 01075 } 01076 01077 static QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel *> proxiesAndModel( const QAbstractItemModel *model ) 01078 { 01079 QList<const QAbstractProxyModel *> proxyChain; 01080 const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>( model ); 01081 const QAbstractItemModel *_model = model; 01082 while ( proxy ) 01083 { 01084 proxyChain.prepend( proxy ); 01085 _model = proxy->sourceModel(); 01086 proxy = qobject_cast<const QAbstractProxyModel *>( _model ); 01087 } 01088 01089 const EntityTreeModel *etm = qobject_cast<const EntityTreeModel *>( _model ); 01090 return qMakePair(proxyChain, etm); 01091 } 01092 01093 static QModelIndex proxiedIndex( const QModelIndex &idx, QList<const QAbstractProxyModel *> proxyChain ) 01094 { 01095 QListIterator<const QAbstractProxyModel *> it( proxyChain ); 01096 QModelIndex _idx = idx; 01097 while ( it.hasNext() ) 01098 _idx = it.next()->mapFromSource( _idx ); 01099 return _idx; 01100 } 01101 01102 QModelIndex EntityTreeModel::modelIndexForCollection( const QAbstractItemModel *model, const Collection &collection ) 01103 { 01104 QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel*> pair = proxiesAndModel( model ); 01105 QModelIndex idx = pair.second->d_ptr->indexForCollection( collection ); 01106 return proxiedIndex( idx, pair.first ); 01107 } 01108 01109 QModelIndexList EntityTreeModel::modelIndexesForItem( const QAbstractItemModel *model, const Item &item ) 01110 { 01111 QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel*> pair = proxiesAndModel( model ); 01112 QModelIndexList list = pair.second->d_ptr->indexesForItem( item ); 01113 QModelIndexList proxyList; 01114 foreach( const QModelIndex &idx, list ) 01115 { 01116 const QModelIndex pIdx = proxiedIndex( idx, pair.first ); 01117 if ( pIdx.isValid() ) 01118 proxyList << pIdx; 01119 } 01120 return proxyList; 01121 } 01122 01123 #include "entitytreemodel.moc"