• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.11.3 API Reference
  • KDE Home
  • Contact Us
 

akonadi/kmime

  • akonadi
  • kmime
messagethreaderproxymodel.cpp
1 /*
2  Copyright (c) 2007 Bruno Virlet <bruno.virlet@gmail.com>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "messagethreaderproxymodel.h"
21 #include "messagethreadingattribute.h"
22 #include "messagemodel.h"
23 
24 #include <akonadi/attributefactory.h>
25 #include <akonadi/itemfetchjob.h>
26 #include <akonadi/itemfetchscope.h>
27 
28 #include <QtCore/QDebug>
29 #include <QtCore/QString>
30 #include <QtCore/QStringList>
31 #include <QtCore/QHash>
32 #include <QtCore/QTime>
33 #include <QtCore/QModelIndex>
34 
35 using namespace Akonadi;
36 
37 class MessageThreaderProxyModel::Private
38 {
39  public:
40  Private( MessageThreaderProxyModel *parent )
41  : mParent( parent )
42  {
43  }
44 
45 
46  MessageModel* sourceMessageModel()
47  {
48  return dynamic_cast<MessageModel*>( mParent->sourceModel() );
49  }
50 
51  /*
52  * Reset everything
53  */
54  void slotCollectionChanged()
55  {
56  childrenMap.clear();
57  indexMap.clear();
58  parentMap.clear();
59  realPerfectParentsMap.clear();
60  realUnperfectParentsMap.clear();
61  realSubjectParentsMap.clear();
62 
63  realPerfectChildrenMap.clear();
64  realUnperfectChildrenMap.clear();
65  realSubjectChildrenMap.clear();
66 
67  mParent->reset();
68  }
69 
70  /*
71  * Function called when the signal rowsInserted was triggered in the
72  * source model.
73  */
74  void slotInsertRows( const QModelIndex& sourceIndex, int begin, int end )
75  {
76  Q_UNUSED( sourceIndex ); // parent source index is always invalid (flat source model)
77  QTime time;
78  time.start();
79 
80  for ( int i=begin; i <= end; i++ )
81  {
82  // Retrieve the item from the source model
83  Item item = sourceMessageModel()->itemForIndex( sourceMessageModel()->index( i, 0 ) );
84  Entity::Id id = item.id();
85  // Get his best potential parent using the mail threader parts
86  readParentsFromParts( item );
87  Entity::Id parentId = parentForItem( item.id() );
88 
89  /*
90  * Fill in the tree maps
91  */
92  int row = childrenMap[ parentId ].count();
93  mParent->beginInsertRows( indexMap[ parentId ], row, row );
94  childrenMap[ parentId ] << item.id();
95  parentMap[ id ] = parentId;
96  QModelIndex index = mParent->createIndex( childrenMap[ parentId ].count() - 1, 0, id );
97  mParent->endInsertRows();
98 
99 
100  /*
101  * Look for potential children into real children map
102  */
103  QList<Entity::Id> potentialChildren = realPerfectChildrenMap[ id ]
104  << realUnperfectChildrenMap[ id ]
105  << realSubjectChildrenMap[ id ];
106  foreach( Entity::Id potentialChildId, potentialChildren ) {
107  // This item can be a child of our item if:
108  // - it's not the item itself (could we do that check when building the 'real' maps ?)
109  // - his parent is set
110  // - and this parent is not already our item
111  if ( potentialChildId != id &&
112  parentMap.constFind( potentialChildId ) != parentMap.constEnd() &&
113  parentMap[ potentialChildId ] != id &&
114  parentMap[ potentialChildId ]
115  )
116 
117  {
118  // Check that the current parent of this item is not better than ours
119  QList<Entity::Id> realParentsList = realPerfectParentsMap[ potentialChildId ]
120  << realUnperfectParentsMap[ potentialChildId ]
121  << realSubjectParentsMap[ potentialChildId ];
122  int currentParentPos = realParentsList.indexOf( parentMap[ potentialChildId ] );
123  // currentParentPos = 0 is probably the more common case so we may avoid an indexOf.
124  if ( currentParentPos == 0 || ( currentParentPos != -1 && realParentsList.indexOf( id ) > currentParentPos ) )
125  // (currentParentPos can be -1 if parent is root)
126  continue;
127 
128  // Remove the children from the old location
129  int childRow = childrenMap[ parentMap[ potentialChildId ] ].indexOf( potentialChildId );
130  mParent->beginRemoveRows( indexMap[ parentMap[ potentialChildId ] ], childRow, childRow );
131  mParent->endRemoveRows();
132  childrenMap[ parentMap[ potentialChildId ] ].removeAt( childRow );
133 
134  // Change the tree info
135  mParent->beginInsertRows( index, childrenMap[ id ].count(), childrenMap[ id ].count() );
136  parentMap[ potentialChildId ] = id;
137  childrenMap[ id ] << potentialChildId;
138 
139  // Recreate index because row change
140  mParent->createIndex( childrenMap[ id ].count() - 1, 0, potentialChildId );
141  mParent->endInsertRows();
142  }
143  }
144  }
145 
146  qDebug() << time.elapsed() << "ms for" << end - begin + 1 << "items";
147  }
148 
149  /*
150  * Function called when the signal rowsAboutToBeRemoved is sent by the source model
151  * (source model indexes are *still* valid)
152  */
153  void slotRemoveRows( const QModelIndex& sourceIndex, int begin, int end )
154  {
155  Q_UNUSED( sourceIndex );
156  for ( int i = begin; i <= end; i++ )
157  {
158  Item item = sourceMessageModel()->itemForIndex( sourceMessageModel()->index( i, 0 ) );
159  Entity::Id id = item.id();
160  Entity::Id parentId = parentMap[ id ];
161  int row = childrenMap[ parentId ].indexOf( id );
162 
163  // Reparent the children to the closest parent
164  foreach( Entity::Id childId, childrenMap[ id ] ) {
165  int childRow = childrenMap[ id ].indexOf( childId );
166  mParent->beginRemoveRows( indexMap[ id ], childRow, childRow );
167  childrenMap[ id ].removeAll( childId ); // There is only one ...
168  mParent->endRemoveRows();
169 
170  mParent->beginInsertRows( indexMap[ parentId ], childrenMap[ parentId ].count(),
171  childrenMap[ parentId ].count() );
172  parentMap[ childId ] = parentId;
173  childrenMap[ parentId ] << childId;
174  mParent->endInsertRows();
175 
176  mParent->createIndex( childrenMap[ parentId ].count() - 1, 0, childId ); // Is it necessary to recreate the index ?
177  }
178 
179  mParent->beginRemoveRows( indexMap[ parentId ], row, row );
180  childrenMap[ parentId ].removeAll( id ); // Remove this id from the children of parentId
181  parentMap.remove( id );
182  indexMap.remove( id );
183  mParent->endRemoveRows();
184 // mParent->beginRemoveColumns( indexMap[ parentId ], 0, sourceMessageModel()->columnCount() - 1 );
185 // mParent->endRemoveColumns();
186  }
187  }
188 
189  /*
190  * This item has his parents stored in his threading parts.
191  * Read them and store them in the 'real' maps.
192  *
193  * We store both relationships :
194  * - child -> parents ( real*ParentsMap )
195  * - parent -> children ( real*ChildrenMap )
196  */
197  void readParentsFromParts( const Item& item )
198  {
199  MessageThreadingAttribute *attr = item.attribute<MessageThreadingAttribute>();
200  if ( attr ) {
201  QList<Entity::Id> realPerfectParentsList = attr->perfectParents();
202  QList<Entity::Id> realUnperfectParentsList = attr->unperfectParents();
203  QList<Entity::Id> realSubjectParentsList = attr->subjectParents();
204 
205  realPerfectParentsMap[ item.id() ] = realPerfectParentsList;
206  realUnperfectParentsMap[ item.id() ] = realUnperfectParentsList;
207  realSubjectParentsMap[ item.id() ] = realSubjectParentsList;
208 
209  // Fill in the children maps
210  foreach( Entity::Id parentId, realPerfectParentsList )
211  realPerfectChildrenMap[ parentId ] << item.id();
212  foreach( Entity::Id parentId, realUnperfectParentsList )
213  realUnperfectChildrenMap[ parentId ] << item.id();
214  foreach( Entity::Id parentId, realSubjectParentsList )
215  realSubjectChildrenMap[ parentId ] << item.id();
216  }
217  }
218 
219  /*
220  * Find the first parent in the parents maps which is actually in the current collection
221  * @param id the item id
222  * @returns the parent id
223  */
224  Entity::Id parentForItem( Entity::Id id )
225  {
226 
227  QList<Entity::Id> parentsIds;
228  parentsIds << realPerfectParentsMap[ id ] << realUnperfectParentsMap[ id ] << realSubjectParentsMap[ id ];
229 
230  foreach( Entity::Id parentId, parentsIds )
231  {
232  // Check that the parent is in the collection
233  // This is time consuming but ... required.
234  if ( sourceMessageModel()->indexForItem( Item( parentId ), 0 ).isValid() )
235  return parentId;
236 
237  }
238 
239  // TODO Check somewhere for 'parent loops' : in the parts, an item child of his child ...
240  return -1;
241  }
242 
243  // -1 is an invalid id which means 'root'
244  Entity::Id idForIndex( const QModelIndex& index )
245  {
246  return index.isValid() ? index.internalId() : -1;
247  }
248 
249  MessageThreaderProxyModel *mParent;
250 
251  /*
252  * These maps store the current tree structure, as presented in the view.
253  * It tries to be as close as possible from the real structure, given that not every parents
254  * are present in the collection
255  */
256  QHash<Entity::Id, QList<Entity::Id> > childrenMap;
257  QHash<Entity::Id, Entity::Id> parentMap;
258  QHash<Entity::Id, QModelIndex> indexMap;
259 
260  /*
261  * These maps store the real parents, as read from the item parts
262  * In the best case, the list should contain only one element ( = unique parent )
263  * If there isn't only one, the algorithm will pick up the first one in the current collection
264  */
265  QHash<Entity::Id, QList<Entity::Id> > realPerfectParentsMap;
266  QHash<Entity::Id, QList<Entity::Id> > realUnperfectParentsMap;
267  QHash<Entity::Id, QList<Entity::Id> > realSubjectParentsMap;
268 
269  QHash<Entity::Id, QList<Entity::Id> > realPerfectChildrenMap;
270  QHash<Entity::Id, QList<Entity::Id> > realUnperfectChildrenMap;
271  QHash<Entity::Id, QList<Entity::Id> > realSubjectChildrenMap;
272 };
273 
274 MessageThreaderProxyModel::MessageThreaderProxyModel( QObject *parent )
275  : QAbstractProxyModel( parent ),
276  d( new Private( this ) )
277 {
278  AttributeFactory::registerAttribute<MessageThreadingAttribute>();
279 }
280 
281 MessageThreaderProxyModel::~MessageThreaderProxyModel()
282 {
283  delete d;
284 }
285 
286 QModelIndex MessageThreaderProxyModel::index( int row, int column, const QModelIndex& parent ) const
287 {
288  Entity::Id parentId = d->idForIndex( parent );
289 
290  if ( row < 0
291  || column < 0
292  || row >= d->childrenMap[ parentId ].count()
293  || column >= columnCount( parent )
294  )
295  return QModelIndex();
296 
297  Entity::Id id = d->childrenMap[ parentId ].at( row );
298 
299  return createIndex( row, column, id );
300 }
301 
302 QModelIndex MessageThreaderProxyModel::parent( const QModelIndex & index ) const
303 {
304  if ( !index.isValid() )
305  return QModelIndex();
306 
307  Entity::Id parentId = d->parentMap[ index.internalId() ];
308 
309  if ( parentId == -1 )
310  return QModelIndex();
311 
312 // int parentParentId = d->parentMap[ parentId ];
313  //int row = d->childrenMap[ parentParentId ].indexOf( parentId );
314  return d->indexMap[ d->parentMap[ index.internalId() ] ];
315  //return createIndex( row, 0, parentId );
316 }
317 
318 QModelIndex MessageThreaderProxyModel::mapToSource( const QModelIndex& index ) const
319 {
320  // This function is slow because it relies on rowForItem in the ItemModel (linear time)
321  return d->sourceMessageModel()->indexForItem( Item( index.internalId() ), index.column() );
322 }
323 
324 QModelIndex MessageThreaderProxyModel::mapFromSource( const QModelIndex& index ) const
325 {
326  Item item = d->sourceMessageModel()->itemForIndex( index );
327  Entity::Id id = item.id();
328  //return d->indexMap[ id ]; // FIXME take column in account like mapToSource
329  return MessageThreaderProxyModel::index( d->indexMap[ id ].row(), index.column(), d->indexMap[ id ].parent() );
330 }
331 
332 QModelIndex MessageThreaderProxyModel::createIndex( int row, int column, quint32 internalId ) const
333 {
334  QModelIndex index = QAbstractProxyModel::createIndex( row, column, internalId );
335  if ( column == 0 )
336  d->indexMap[ internalId ] = index; // Store the newly created index in the index map
337  return index;
338 }
339 
340 void MessageThreaderProxyModel::setSourceModel( QAbstractItemModel* model )
341 {
342  // TODO Assert model is a MessageModel
343  QAbstractProxyModel::setSourceModel( model );
344 
345  d->sourceMessageModel()->fetchScope().fetchAttribute<MessageThreadingAttribute>();
346 
347  // TODO disconnect old model
348  connect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotInsertRows(QModelIndex,int,int)) );
349  connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(slotRemoveRows(QModelIndex,int,int)) );
350  connect( d->sourceMessageModel(), SIGNAL(collectionChanged(Akonadi::Collection)), SLOT(slotCollectionChanged()) );
351 }
352 
353 
354 bool MessageThreaderProxyModel::hasChildren( const QModelIndex& index ) const
355 {
356  return rowCount( index ) > 0;
357 }
358 
359 int MessageThreaderProxyModel::columnCount( const QModelIndex& ) const
360 {
361  // We assume that the source model has the same number of columns for each rows
362  return sourceModel()->columnCount( QModelIndex() );
363 }
364 
365 int MessageThreaderProxyModel::rowCount( const QModelIndex& index ) const
366 {
367  Entity::Id id = d->idForIndex( index );
368  if ( id == -1 )
369  return d->childrenMap[ -1 ].count();
370 
371  if ( index.column() == 0 ) // QModelIndex() has children
372  return d->childrenMap[ id ].count();
373 
374  return 0;
375 }
376 
377 QStringList MessageThreaderProxyModel::mimeTypes() const
378 {
379  return d->sourceMessageModel()->mimeTypes();
380 }
381 
382 QMimeData *MessageThreaderProxyModel::mimeData(const QModelIndexList &indexes) const
383 {
384  QModelIndexList sourceIndexes;
385  for (int i = 0; i < indexes.count(); i++)
386  sourceIndexes << mapToSource( indexes.at(i) );
387 
388  return sourceModel()->mimeData(sourceIndexes);
389 }
390 
391 #include "moc_messagethreaderproxymodel.cpp"
Akonadi::MessageThreaderProxyModel::setSourceModel
void setSourceModel(QAbstractItemModel *sourceMessageModel)
Set the source model.
Definition: messagethreaderproxymodel.cpp:340
Akonadi::MessageThreaderProxyModel::mimeTypes
QStringList mimeTypes() const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:377
Akonadi::MessageThreaderProxyModel::createIndex
QModelIndex createIndex(int row, int column, quint32 internalId) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:332
Akonadi::MessageThreadingAttribute::subjectParents
QList< Item::Id > subjectParents() const
Returns the list of possible parent message ids based on analyzing the subject.
Definition: messagethreadingattribute.cpp:127
Akonadi::MessageThreaderProxyModel::mapToSource
QModelIndex mapToSource(const QModelIndex &index) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:318
Akonadi::MessageThreaderProxyModel::rowCount
int rowCount(const QModelIndex &index) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:365
Akonadi::MessageThreaderProxyModel
Proxy to thread message using the Mailthreader agent.
Definition: messagethreaderproxymodel.h:38
Akonadi::MessageThreaderProxyModel::mapFromSource
QModelIndex mapFromSource(const QModelIndex &index) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:324
Akonadi::MessageThreadingAttribute
Message threading information.
Definition: messagethreadingattribute.h:34
Akonadi::MessageThreaderProxyModel::columnCount
int columnCount(const QModelIndex &index) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:359
Akonadi::MessageThreaderProxyModel::parent
QModelIndex parent(const QModelIndex &index) const
Reimplemented to actually do the threading.
Definition: messagethreaderproxymodel.cpp:302
Akonadi::MessageThreaderProxyModel::mimeData
QMimeData * mimeData(const QModelIndexList &indexes) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:382
Akonadi::MessageThreadingAttribute::perfectParents
QList< Item::Id > perfectParents() const
Returns the list of perfect parent message ids.
Definition: messagethreadingattribute.cpp:107
Akonadi::MessageThreadingAttribute::unperfectParents
QList< Item::Id > unperfectParents() const
Returns the list of non-perfect parent message ids.
Definition: messagethreadingattribute.cpp:117
Akonadi::MessageThreaderProxyModel::~MessageThreaderProxyModel
virtual ~MessageThreaderProxyModel()
Destroy the model.
Definition: messagethreaderproxymodel.cpp:281
Akonadi::MessageModel
A flat self-updating message model.
Definition: messagemodel.h:38
Akonadi::MessageThreaderProxyModel::MessageThreaderProxyModel
MessageThreaderProxyModel(QObject *parent=0)
Create a new MessageThreaderProxyModel.
Definition: messagethreaderproxymodel.cpp:274
Akonadi::MessageThreaderProxyModel::hasChildren
bool hasChildren(const QModelIndex &index) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:354
Akonadi::MessageThreaderProxyModel::index
QModelIndex index(int row, int column, const QModelIndex &parent) const
Reimplemented.
Definition: messagethreaderproxymodel.cpp:286
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Tue Nov 26 2013 09:03:54 by doxygen 1.8.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi/kmime

Skip menu "akonadi/kmime"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.11.3 API Reference

Skip menu "kdepimlibs-4.11.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • 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