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

akonadi

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

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



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