• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.8.3 API Reference
  • 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     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 May 14 2012 04:52:54 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

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

kdepimlibs-4.8.3 API Reference

Skip menu "kdepimlibs-4.8.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal