• Skip to content
  • Skip to link menu
KDE 4.7 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

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"

akonadi

Skip menu "akonadi"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Modules
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal