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"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon May 14 2012 04:52:58 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon May 14 2012 04:52:58 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.