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

akonadi

messagethreaderproxymodel.cpp

00001 /*
00002     Copyright (c) 2007 Bruno Virlet <bruno.virlet@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 "messagethreaderproxymodel.h"
00021 #include "messagethreadingattribute.h"
00022 #include "messagemodel.h"
00023 
00024 #include <akonadi/attributefactory.h>
00025 #include <akonadi/itemfetchjob.h>
00026 #include <akonadi/itemfetchscope.h>
00027 
00028 #include <QtCore/QDebug>
00029 #include <QtCore/QString>
00030 #include <QtCore/QStringList>
00031 #include <QtCore/QHash>
00032 #include <QtCore/QTime>
00033 #include <QtCore/QModelIndex>
00034 
00035 using namespace Akonadi;
00036 
00037 class MessageThreaderProxyModel::Private
00038 {
00039   public:
00040     Private( MessageThreaderProxyModel *parent )
00041       : mParent( parent )
00042     {
00043     }
00044 
00045 
00046   MessageModel* sourceMessageModel()
00047   {
00048     return dynamic_cast<MessageModel*>( mParent->sourceModel() );
00049   }
00050 
00051   /*
00052    * Reset everything
00053    */
00054   void slotCollectionChanged()
00055   {
00056     childrenMap.clear();
00057     indexMap.clear();
00058     parentMap.clear();
00059     realPerfectParentsMap.clear();
00060     realUnperfectParentsMap.clear();
00061     realSubjectParentsMap.clear();
00062 
00063     realPerfectChildrenMap.clear();
00064     realUnperfectChildrenMap.clear();
00065     realSubjectChildrenMap.clear();
00066 
00067     mParent->reset();
00068   }
00069 
00070   /*
00071    * Function called when the signal rowsInserted was triggered in the
00072    * source model.
00073    */
00074   void slotInsertRows( const QModelIndex& sourceIndex, int begin, int end )
00075   {
00076     Q_UNUSED( sourceIndex ); // parent source index is always invalid (flat source model)
00077     QTime time;
00078     time.start();
00079 
00080     for ( int i=begin; i <= end; i++ )
00081     {
00082       // Retrieve the item from the source model
00083       Item item = sourceMessageModel()->itemForIndex( sourceMessageModel()->index( i, 0 ) );
00084       Entity::Id id = item.id();
00085       // Get his best potential parent using the mail threader parts
00086       readParentsFromParts( item );
00087       Entity::Id parentId = parentForItem( item.id() );
00088 
00089       /*
00090        * Fill in the tree maps
00091        */
00092       int row = childrenMap[ parentId ].count();
00093       mParent->beginInsertRows( indexMap[ parentId ], row, row );
00094       childrenMap[ parentId ] << item.id();
00095       parentMap[ id ] = parentId;
00096       QModelIndex index = mParent->createIndex( childrenMap[ parentId ].count() - 1, 0, id );
00097       mParent->endInsertRows();
00098 
00099 
00100       /*
00101        * Look for potential children into real children map
00102        */
00103       QList<Entity::Id> potentialChildren = realPerfectChildrenMap[ id ]
00104                                          << realUnperfectChildrenMap[ id ]
00105                                          << realSubjectChildrenMap[ id ];
00106       foreach( Entity::Id potentialChildId, potentialChildren ) {
00107           // This item can be a child of our item if:
00108           // - it's not the item itself (could we do that check when building the 'real' maps ?)
00109           // - his parent is set
00110           // -   and this parent is not already our item
00111           if ( potentialChildId != id &&
00112                parentMap.constFind( potentialChildId ) != parentMap.constEnd() &&
00113                parentMap[ potentialChildId ] != id &&
00114                parentMap[ potentialChildId ]
00115             )
00116 
00117           {
00118             // Check that the current parent of this item is not better than ours
00119             QList<Entity::Id> realParentsList = realPerfectParentsMap[ potentialChildId ]
00120                                          << realUnperfectParentsMap[ potentialChildId ]
00121                                          << realSubjectParentsMap[ potentialChildId ];
00122             int currentParentPos = realParentsList.indexOf( parentMap[ potentialChildId ] );
00123             // currentParentPos = 0 is probably the more common case so we may avoid an indexOf.
00124             if ( currentParentPos == 0 || ( currentParentPos != -1 && realParentsList.indexOf( id ) > currentParentPos ) )
00125               // (currentParentPos can be -1 if parent is root)
00126               continue;
00127 
00128             // Remove the children from the old location
00129             int childRow = childrenMap[ parentMap[ potentialChildId ] ].indexOf( potentialChildId );
00130             mParent->beginRemoveRows( indexMap[ parentMap[ potentialChildId ] ], childRow, childRow );
00131             mParent->endRemoveRows();
00132             childrenMap[ parentMap[ potentialChildId ] ].removeAt( childRow );
00133 
00134             // Change the tree info
00135             mParent->beginInsertRows( index, childrenMap[ id ].count(), childrenMap[ id ].count() );
00136             parentMap[ potentialChildId ] = id;
00137             childrenMap[ id ] << potentialChildId;
00138 
00139             // Recreate index because row change
00140             mParent->createIndex( childrenMap[ id ].count() - 1, 0, potentialChildId );
00141             mParent->endInsertRows();
00142           }
00143       }
00144     }
00145 
00146     qDebug() << time.elapsed() << "ms for" << end - begin + 1 << "items";
00147   }
00148 
00149   /*
00150    * Function called when the signal rowsAboutToBeRemoved is sent by the source model
00151    * (source model indexes are *still* valid)
00152    */
00153   void slotRemoveRows( const QModelIndex& sourceIndex, int begin, int end )
00154   {
00155     Q_UNUSED( sourceIndex );
00156     for ( int i = begin; i <= end; i++ )
00157     {
00158       Item item = sourceMessageModel()->itemForIndex( sourceMessageModel()->index( i, 0 ) );
00159       Entity::Id id = item.id();
00160       Entity::Id parentId = parentMap[ id ];
00161       int row = childrenMap[ parentId ].indexOf( id );
00162 
00163       // Reparent the children to the closest parent
00164       foreach( Entity::Id childId, childrenMap[ id ] ) {
00165         int childRow = childrenMap[ id ].indexOf( childId );
00166         mParent->beginRemoveRows( indexMap[ id ], childRow, childRow );
00167         childrenMap[ id ].removeAll( childId ); // There is only one ...
00168         mParent->endRemoveRows();
00169 
00170         mParent->beginInsertRows( indexMap[ parentId ], childrenMap[ parentId ].count(),
00171                                   childrenMap[ parentId ].count() );
00172         parentMap[ childId ] = parentId;
00173         childrenMap[ parentId ] << childId;
00174         mParent->endInsertRows();
00175 
00176         mParent->createIndex( childrenMap[ parentId ].count() - 1, 0, childId ); // Is it necessary to recreate the index ?
00177       }
00178 
00179       mParent->beginRemoveRows( indexMap[ parentId ], row, row );
00180       childrenMap[ parentId ].removeAll( id ); // Remove this id from the children of parentId
00181       parentMap.remove( id );
00182       indexMap.remove( id );
00183       mParent->endRemoveRows();
00184 //      mParent->beginRemoveColumns( indexMap[ parentId ], 0, sourceMessageModel()->columnCount() - 1 );
00185 //      mParent->endRemoveColumns();
00186     }
00187   }
00188 
00189   /*
00190    * This item has his parents stored in his threading parts.
00191    * Read them and store them in the 'real' maps.
00192    *
00193    * We store both relationships :
00194    * - child -> parents ( real*ParentsMap )
00195    * - parent -> children ( real*ChildrenMap )
00196    */
00197   void readParentsFromParts( const Item& item )
00198   {
00199     MessageThreadingAttribute *attr = item.attribute<MessageThreadingAttribute>();
00200     if ( attr ) {
00201       QList<Entity::Id> realPerfectParentsList = attr->perfectParents();
00202       QList<Entity::Id> realUnperfectParentsList = attr->unperfectParents();
00203       QList<Entity::Id> realSubjectParentsList = attr->subjectParents();
00204 
00205       realPerfectParentsMap[ item.id() ] = realPerfectParentsList;
00206       realUnperfectParentsMap[ item.id() ] = realUnperfectParentsList;
00207       realSubjectParentsMap[ item.id() ] = realSubjectParentsList;
00208 
00209       // Fill in the children maps
00210       foreach( Entity::Id parentId, realPerfectParentsList )
00211         realPerfectChildrenMap[ parentId ] << item.id();
00212       foreach( Entity::Id parentId, realUnperfectParentsList )
00213         realUnperfectChildrenMap[ parentId ] << item.id();
00214       foreach( Entity::Id parentId, realSubjectParentsList )
00215         realSubjectChildrenMap[ parentId ] << item.id();
00216     }
00217   }
00218 
00219   /*
00220    * Find the first parent in the parents maps which is actually in the current collection
00221    * @param id the item id
00222    * @returns the parent id
00223    */
00224   Entity::Id parentForItem( Entity::Id id )
00225   {
00226 
00227     QList<Entity::Id> parentsIds;
00228     parentsIds << realPerfectParentsMap[ id ] << realUnperfectParentsMap[ id ] << realSubjectParentsMap[ id ];
00229 
00230     foreach( Entity::Id parentId, parentsIds )
00231     {
00232     // Check that the parent is in the collection
00233     // This is time consuming but ... required.
00234     if ( sourceMessageModel()->indexForItem( Item( parentId ), 0 ).isValid() )
00235       return parentId;
00236 
00237     }
00238 
00239     // TODO Check somewhere for 'parent loops' : in the parts, an item child of his child ...
00240     return -1;
00241   }
00242 
00243   // -1 is an invalid id which means 'root'
00244   Entity::Id idForIndex( const QModelIndex& index )
00245   {
00246     return index.isValid() ? index.internalId() : -1;
00247   }
00248 
00249   MessageThreaderProxyModel *mParent;
00250 
00251   /*
00252    * These maps store the current tree structure, as presented in the view.
00253    * It tries to be as close as possible from the real structure, given that not every parents
00254    * are present in the collection
00255    */
00256   QHash<Entity::Id, QList<Entity::Id> > childrenMap;
00257   QHash<Entity::Id, Entity::Id> parentMap;
00258   QHash<Entity::Id, QModelIndex> indexMap;
00259 
00260   /*
00261    * These maps store the real parents, as read from the item parts
00262    * In the best case, the list should contain only one element ( = unique parent )
00263    * If there isn't only one, the algorithm will pick up the first one in the current collection
00264    */
00265   QHash<Entity::Id, QList<Entity::Id> > realPerfectParentsMap;
00266   QHash<Entity::Id, QList<Entity::Id> > realUnperfectParentsMap;
00267   QHash<Entity::Id, QList<Entity::Id> > realSubjectParentsMap;
00268 
00269   QHash<Entity::Id, QList<Entity::Id> > realPerfectChildrenMap;
00270   QHash<Entity::Id, QList<Entity::Id> > realUnperfectChildrenMap;
00271   QHash<Entity::Id, QList<Entity::Id> > realSubjectChildrenMap;
00272 };
00273 
00274 MessageThreaderProxyModel::MessageThreaderProxyModel( QObject *parent )
00275   : QAbstractProxyModel( parent ),
00276     d( new Private( this ) )
00277 {
00278   AttributeFactory::registerAttribute<MessageThreadingAttribute>();
00279 }
00280 
00281 MessageThreaderProxyModel::~MessageThreaderProxyModel()
00282 {
00283   delete d;
00284 }
00285 
00286 QModelIndex MessageThreaderProxyModel::index( int row, int column, const QModelIndex& parent ) const
00287 {
00288   Entity::Id parentId = d->idForIndex( parent );
00289 
00290   if ( row < 0
00291        || column < 0
00292        || row >= d->childrenMap[ parentId ].count()
00293        || column >= columnCount( parent )
00294        )
00295     return QModelIndex();
00296 
00297   Entity::Id id = d->childrenMap[ parentId ].at( row );
00298 
00299   return createIndex( row, column, id );
00300 }
00301 
00302 QModelIndex MessageThreaderProxyModel::parent( const QModelIndex & index ) const
00303 {
00304   if ( !index.isValid() )
00305     return QModelIndex();
00306 
00307   Entity::Id parentId = d->parentMap[ index.internalId() ];
00308 
00309   if ( parentId == -1 )
00310     return QModelIndex();
00311 
00312 //  int parentParentId = d->parentMap[ parentId ];
00313   //int row = d->childrenMap[ parentParentId ].indexOf( parentId );
00314   return d->indexMap[ d->parentMap[ index.internalId() ] ];
00315     //return createIndex( row, 0, parentId );
00316 }
00317 
00318 QModelIndex MessageThreaderProxyModel::mapToSource( const QModelIndex& index ) const
00319 {
00320   // This function is slow because it relies on rowForItem in the ItemModel (linear time)
00321   return d->sourceMessageModel()->indexForItem( Item( index.internalId() ), index.column() );
00322 }
00323 
00324 QModelIndex MessageThreaderProxyModel::mapFromSource( const QModelIndex& index ) const
00325 {
00326   Item item = d->sourceMessageModel()->itemForIndex( index );
00327   Entity::Id id = item.id();
00328   //return d->indexMap[ id  ]; // FIXME take column in account like mapToSource
00329   return MessageThreaderProxyModel::index( d->indexMap[ id ].row(), index.column(), d->indexMap[ id ].parent() );
00330 }
00331 
00332 QModelIndex MessageThreaderProxyModel::createIndex( int row, int column, quint32 internalId ) const
00333 {
00334   QModelIndex index = QAbstractProxyModel::createIndex( row, column, internalId );
00335   if ( column == 0 )
00336     d->indexMap[ internalId ] = index; // Store the newly created index in the index map
00337   return index;
00338 }
00339 
00340 void MessageThreaderProxyModel::setSourceModel( QAbstractItemModel* model )
00341 {
00342   // TODO Assert model is a MessageModel
00343   QAbstractProxyModel::setSourceModel( model );
00344 
00345   d->sourceMessageModel()->fetchScope().fetchAttribute<MessageThreadingAttribute>();
00346 
00347   // TODO disconnect old model
00348   connect( sourceModel(), SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( slotInsertRows( QModelIndex, int, int ) ) );
00349   connect( sourceModel(), SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ), SLOT( slotRemoveRows( QModelIndex, int, int ) ) );
00350   connect( d->sourceMessageModel(), SIGNAL( collectionChanged( Akonadi::Collection ) ), SLOT( slotCollectionChanged() ) );
00351 }
00352 
00353 
00354 bool MessageThreaderProxyModel::hasChildren( const QModelIndex& index ) const
00355 {
00356   return rowCount( index ) > 0;
00357 }
00358 
00359 int MessageThreaderProxyModel::columnCount( const QModelIndex& ) const
00360 {
00361   // We assume that the source model has the same number of columns for each rows
00362   return sourceModel()->columnCount( QModelIndex() );
00363 }
00364 
00365 int MessageThreaderProxyModel::rowCount( const QModelIndex& index ) const
00366 {
00367   Entity::Id id = d->idForIndex( index );
00368   if ( id == -1 )
00369     return d->childrenMap[ -1 ].count();
00370 
00371   if ( index.column() == 0 ) // QModelIndex() has children
00372     return d->childrenMap[ id ].count();
00373 
00374   return 0;
00375 }
00376 
00377 QStringList MessageThreaderProxyModel::mimeTypes() const
00378 {
00379   return d->sourceMessageModel()->mimeTypes();
00380 }
00381 
00382 QMimeData *MessageThreaderProxyModel::mimeData(const QModelIndexList &indexes) const
00383 {
00384     QModelIndexList sourceIndexes;
00385     for (int i = 0; i < indexes.count(); i++)
00386         sourceIndexes << mapToSource( indexes.at(i) );
00387 
00388     return sourceModel()->mimeData(sourceIndexes);
00389 }
00390 
00391 #include "messagethreaderproxymodel.moc"

akonadi

Skip menu "akonadi"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • 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.3
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