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