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

akonadi

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

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 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