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