~ubuntu-branches/ubuntu/oneiric/kdepim/oneiric-updates

« back to all changes in this revision

Viewing changes to mobile/lib/threadmodel.cpp

  • Committer: Package Import Robot
  • Author(s): Philip Muškovac
  • Date: 2011-06-28 19:33:24 UTC
  • mfrom: (0.2.13) (0.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20110628193324-8yvjs8sdv9rdoo6c
Tags: 4:4.7.0-0ubuntu1
* New upstream release
  - update install files
  - add missing kdepim-doc package to control file
  - Fix Vcs lines
  - kontact breaks/replaces korganizer << 4:4.6.80
  - tighten the dependency of kdepim-dev on libkdepim4 to fix lintian error

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    Copyright (C) 2010 Klarälvdalens Datakonsult AB,
 
3
        a KDAB Group company, info@kdab.net,
 
4
        author Stephen Kelly <stephen@kdab.com>
 
5
 
 
6
    This library is free software; you can redistribute it and/or modify it
 
7
    under the terms of the GNU Library General Public License as published by
 
8
    the Free Software Foundation; either version 2 of the License, or (at your
 
9
    option) any later version.
 
10
 
 
11
    This library is distributed in the hope that it will be useful, but WITHOUT
 
12
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
13
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
 
14
    License for more details.
 
15
 
 
16
    You should have received a copy of the GNU Library General Public License
 
17
    along with this library; see the file COPYING.LIB.  If not, write to the
 
18
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
19
    02110-1301, USA.
 
20
*/
 
21
 
 
22
#include "threadmodel.h"
 
23
 
 
24
#include "rangemanager_p.h"
 
25
#include "threadgroupermodel.h"
 
26
 
 
27
#include <akonadi/kmime/messagestatus.h>
 
28
#include <kmime/kmime_message.h>
 
29
 
 
30
class ThreadModelPrivate
 
31
{
 
32
  ThreadModelPrivate( QAbstractItemModel *emailModel, ThreadModel *qq )
 
33
    : q_ptr( qq ), m_emailModel( emailModel )
 
34
  {
 
35
  }
 
36
 
 
37
  Q_DECLARE_PUBLIC( ThreadModel )
 
38
  ThreadModel* const q_ptr;
 
39
 
 
40
  void slotRowsInserted( const QModelIndex&, int, int );
 
41
  void slotRowsRemoved( const QModelIndex&, int, int );
 
42
  void slotResetModel();
 
43
  void populateThreadModel();
 
44
 
 
45
  QAbstractItemModel *m_emailModel;
 
46
  RangeManager m_rangeManager;
 
47
};
 
48
 
 
49
void ThreadModelPrivate::slotRowsInserted( const QModelIndex&, int start, int end )
 
50
{
 
51
  /**
 
52
   * At this point the m_emailModel contains the new rows already, but the range manager
 
53
   * is not updated yet.
 
54
   *
 
55
   * Example:
 
56
   *   The m_emailModel contained two threads each with two messages
 
57
   *     EM[t1 t1 t2 t2]
 
58
   *   so the range manager contains the same information
 
59
   *     RM[t1 t1 t2 t2]
 
60
   *   Now 4 new rows are inserted into the m_emailModel with one new message belonging to t1,
 
61
   *   one new message belonging to t2 and 2 new messages that are thread leaders themself.
 
62
   *   The m_emailModel will look like the following now
 
63
   *     EM[t1 t1 t1 t2 t3 t4 t4 t4]
 
64
   *   and we have to adapt the range manager to match this.
 
65
   */
 
66
 
 
67
  // At first find out if there exists a row before 'start' and what its thread id and associated range is
 
68
  Akonadi::Item::Id previousThreadId = -1;
 
69
  if ( start != 0 ) {
 
70
    const QModelIndex index = m_emailModel->index( start - 1, 0 );
 
71
    Q_ASSERT( index.isValid() );
 
72
    previousThreadId = index.data( ThreadGrouperModel::ThreadIdRole ).toLongLong();
 
73
  }
 
74
 
 
75
  // check if there exists a row after 'end' and what its thread id and associated range is
 
76
  Akonadi::Item::Id nextThreadId = -1;
 
77
  if ( end != (m_emailModel->rowCount() - 1) ) {
 
78
    const QModelIndex index = m_emailModel->index( end + 1, 0 );
 
79
    Q_ASSERT( index.isValid() );
 
80
    nextThreadId = index.data( ThreadGrouperModel::ThreadIdRole ).toLongLong();
 
81
  }
 
82
 
 
83
  // iterate over the new rows
 
84
  Akonadi::Item::Id currentThreadId = previousThreadId;
 
85
  int currentRange = (start == 0 ? -1 : m_rangeManager.rangeForPosition( start - 1 ));
 
86
 
 
87
  for ( int row = start; row <= end; ++row ) {
 
88
    const QModelIndex index = m_emailModel->index( row, 0 );
 
89
    Q_ASSERT( index.isValid() );
 
90
 
 
91
    const Akonadi::Item::Id threadRootId = index.data( ThreadGrouperModel::ThreadIdRole ).toLongLong();
 
92
    if ( threadRootId == currentThreadId ) { // belongs to the current thread
 
93
      m_rangeManager.increaseRange( currentRange, 1 );
 
94
 
 
95
      const QModelIndex threadIndex = q_ptr->index( currentRange, 0 ); //TODO: cache it
 
96
      emit q_ptr->dataChanged( threadIndex, threadIndex );
 
97
    } else { // threadRootId != currentThreadId
 
98
      if ( (row == end) && (threadRootId == nextThreadId) ) {
 
99
        const int nextRange = currentRange + 1;
 
100
        m_rangeManager.increaseRange( nextRange, 1 );
 
101
 
 
102
        const QModelIndex threadIndex = q_ptr->index( nextRange, 0 );
 
103
        emit q_ptr->dataChanged( threadIndex, threadIndex );
 
104
      } else {
 
105
        currentRange++;
 
106
        q_ptr->beginInsertRows( QModelIndex(), currentRange, currentRange );
 
107
        m_rangeManager.insertRange( currentRange, 1 );
 
108
        q_ptr->endInsertRows();
 
109
      }
 
110
    }
 
111
 
 
112
    currentThreadId = threadRootId;
 
113
  }
 
114
}
 
115
 
 
116
void ThreadModelPrivate::slotRowsRemoved( const QModelIndex&, int start, int end )
 
117
{
 
118
  const int startRange = m_rangeManager.rangeForPosition( start );
 
119
  const int endRange = m_rangeManager.rangeForPosition( end );
 
120
 
 
121
  const int startRangeSize = m_rangeManager.rangeSize( startRange );
 
122
  const int endRangeSize = m_rangeManager.rangeSize( endRange );
 
123
 
 
124
  if ( startRange == endRange ) {
 
125
    // the rows to be removed are covered by one range
 
126
 
 
127
    const int rowCount = (end - start + 1);
 
128
    if ( (rowCount - startRangeSize) == 0 ) { // all messages of this thread are removed -> remove the thread
 
129
      q_ptr->beginRemoveRows( QModelIndex(), startRange, startRange );
 
130
      m_rangeManager.removeRange( startRange );
 
131
      q_ptr->endRemoveRows();
 
132
    } else { // some messages are left in the thread -> adapt thread size
 
133
      m_rangeManager.decreaseRange( startRange, rowCount );
 
134
 
 
135
      const QModelIndex index = q_ptr->index( startRange, 0 );
 
136
      emit q_ptr->dataChanged( index, index ); // the number of thread childs has changed -> trigger view update
 
137
    }
 
138
  } else {
 
139
    // the rows to be removed are covered by two or more ranges
 
140
 
 
141
    // first check how many rows of the start range are affected
 
142
    const int startRangeStart = m_rangeManager.rangeStart( startRange + 1 );
 
143
    const int affectedStartRows = (startRangeStart - start);
 
144
 
 
145
    // check how many rows of the end range are affected
 
146
    const int endRangeStart = m_rangeManager.rangeStart( endRange );
 
147
    const int affectedEndRows = (end - endRangeStart + 1);
 
148
 
 
149
    // we can't delete the ranges one by one, but have to remove them in one go,
 
150
    // so store which is the first and last range to be deleted
 
151
    int startRangeToDelete = startRange + 1;
 
152
    int endRangeToDelete = endRange - 1;
 
153
 
 
154
    // we can't update the indexes before the rows are removed, so delay it
 
155
    bool updateStartRange = false;
 
156
    bool updateEndRange = false;
 
157
 
 
158
    if ( (affectedStartRows - startRangeSize) == 0 ) { // all messages of this thread are removed -> remove the thread
 
159
      startRangeToDelete = startRange;
 
160
    } else { // some messages are left in the thread -> adapt thread size
 
161
      updateStartRange = true;
 
162
    }
 
163
 
 
164
    if ( (affectedEndRows - endRangeSize) == 0 ) { // all messages of this thread are removed -> remove the thread
 
165
      endRangeToDelete = endRange;
 
166
    } else { // some messages are left in the thread -> adapt thread size
 
167
      updateEndRange = true;
 
168
    }
 
169
 
 
170
    // check if there are ranges that must be removed
 
171
    if ( (endRangeToDelete - startRangeToDelete) >= 0 ) {
 
172
      q_ptr->beginRemoveRows( QModelIndex(), startRangeToDelete, endRangeToDelete );
 
173
      for ( int range = startRangeToDelete; range <= endRangeToDelete; ++range ) {
 
174
        m_rangeManager.removeRange( startRangeToDelete );
 
175
      }
 
176
      q_ptr->endRemoveRows();
 
177
    }
 
178
 
 
179
    // no update the start range and end range as well
 
180
    if ( updateStartRange ) {
 
181
      m_rangeManager.decreaseRange( startRange, affectedStartRows );
 
182
 
 
183
      const QModelIndex index = q_ptr->index( startRange, 0 );
 
184
      emit q_ptr->dataChanged( index, index ); // the number of thread childs has changed -> trigger view update
 
185
    }
 
186
 
 
187
    if ( updateEndRange ) {
 
188
      // we need to update the end range here, since the ranges between start and end have been removed already
 
189
      const int updatedEndRange = (endRange - (endRangeToDelete - startRangeToDelete) - 1);
 
190
      m_rangeManager.decreaseRange( updatedEndRange, affectedEndRows );
 
191
 
 
192
      const QModelIndex index = q_ptr->index( updatedEndRange, 0 );
 
193
      emit q_ptr->dataChanged( index, index ); // the number of thread childs has changed -> trigger view update
 
194
    }
 
195
  }
 
196
}
 
197
 
 
198
void ThreadModelPrivate::slotResetModel()
 
199
{
 
200
  populateThreadModel();
 
201
}
 
202
 
 
203
void ThreadModelPrivate::populateThreadModel()
 
204
{
 
205
  Q_Q( ThreadModel );
 
206
  q->beginResetModel();
 
207
 
 
208
  m_rangeManager.clear();
 
209
  const int rowCount = m_emailModel->rowCount();
 
210
  if ( rowCount == 0 ) {
 
211
    q->endResetModel();
 
212
    return;
 
213
  }
 
214
 
 
215
  const QModelIndex firstIndex = m_emailModel->index( 0, 0 );
 
216
  Akonadi::Item::Id currentThreadId = firstIndex.data( ThreadGrouperModel::ThreadIdRole ).toLongLong();
 
217
 
 
218
  int startRow = 0;
 
219
  static const int column = 0;
 
220
  for ( int row = 1; row < rowCount; ++row ) {
 
221
    const QModelIndex index = m_emailModel->index( row, column );
 
222
    Q_ASSERT( index.isValid() );
 
223
 
 
224
    const Akonadi::Item::Id threadRoot = index.data( ThreadGrouperModel::ThreadIdRole ).toLongLong();
 
225
    if ( threadRoot != currentThreadId ) {
 
226
      m_rangeManager.insertRange( m_rangeManager.count(), row - startRow );
 
227
      startRow = row;
 
228
    }
 
229
 
 
230
    currentThreadId = threadRoot;
 
231
  }
 
232
 
 
233
  m_rangeManager.insertRange( m_rangeManager.count(), rowCount - startRow );
 
234
  q->endResetModel();
 
235
}
 
236
 
 
237
ThreadModel::ThreadModel( QAbstractItemModel *emailModel, QObject *parent )
 
238
  : QAbstractListModel( parent ), d_ptr( new ThreadModelPrivate( emailModel, this ) )
 
239
{
 
240
  connect( emailModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
 
241
           this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
 
242
 
 
243
  connect( emailModel, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
 
244
           this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
 
245
 
 
246
  connect( emailModel, SIGNAL( layoutChanged() ),
 
247
           this, SLOT( slotResetModel() ) );
 
248
 
 
249
  connect( emailModel, SIGNAL( modelReset() ),
 
250
           this, SLOT( slotResetModel() ) );
 
251
 
 
252
  QHash<int, QByteArray> roleNames = emailModel->roleNames();
 
253
  roleNames.insert( ThreadSizeRole, "threadSize" );
 
254
  roleNames.insert( ThreadUnreadCountRole, "threadUnreadCount" );
 
255
  setRoleNames( roleNames );
 
256
}
 
257
 
 
258
ThreadModel::~ThreadModel()
 
259
{
 
260
  delete d_ptr;
 
261
}
 
262
 
 
263
QVariant ThreadModel::data( const QModelIndex &index, int role ) const
 
264
{
 
265
  Q_D( const ThreadModel );
 
266
 
 
267
  if ( !index.isValid() )
 
268
    return QVariant();
 
269
 
 
270
  const int indexRow = index.row();
 
271
  if ( indexRow < d->m_rangeManager.count() ) {
 
272
    const int range = indexRow;
 
273
 
 
274
    const int rangeStartRow = d->m_rangeManager.rangeStart( range );
 
275
    const int rangeSize = d->m_rangeManager.rangeSize( range );
 
276
    const QModelIndex firstMailIndex = d->m_emailModel->index( rangeStartRow, 0 );
 
277
    Q_ASSERT( firstMailIndex.isValid() );
 
278
 
 
279
    if ( role == ThreadRangeStartRole )
 
280
      return rangeStartRow;
 
281
    if ( role == ThreadRangeEndRole )
 
282
      return (rangeStartRow + rangeSize - 1);
 
283
    if ( role == ThreadSizeRole )
 
284
      return rangeSize;
 
285
    if ( role == ThreadUnreadCountRole ) {
 
286
      int unreadCount = 0;
 
287
      for ( int row = rangeStartRow; row <= (rangeStartRow + rangeSize - 1); ++row ) {
 
288
        static const int column = 0;
 
289
 
 
290
        const QModelIndex index = d->m_emailModel->index( row, column );
 
291
        Q_ASSERT( index.isValid() );
 
292
 
 
293
        const Akonadi::Item item = index.data( Akonadi::EntityTreeModel::ItemRole ).value<Akonadi::Item>();
 
294
        Q_ASSERT( item.isValid() );
 
295
        Q_ASSERT( item.hasPayload<KMime::Message::Ptr>() );
 
296
 
 
297
        const KMime::Message::Ptr message = item.payload<KMime::Message::Ptr>();
 
298
        Akonadi::MessageStatus status;
 
299
        status.setStatusFromFlags( item.flags() );
 
300
        if ( !status.isRead() )
 
301
          ++unreadCount;
 
302
      }
 
303
 
 
304
      return unreadCount;
 
305
    }
 
306
 
 
307
    if ( role == Qt::DisplayRole ) {
 
308
      const QString displayString = firstMailIndex.data( role ).toString();
 
309
      return QString("(" + QString::number( rangeSize ) + ")" + displayString);
 
310
    }
 
311
 
 
312
    return firstMailIndex.data( role );
 
313
  }
 
314
 
 
315
  return QVariant();
 
316
}
 
317
 
 
318
int ThreadModel::rowCount( const QModelIndex& ) const
 
319
{
 
320
  Q_D( const ThreadModel );
 
321
 
 
322
  return d->m_rangeManager.count();
 
323
}
 
324
 
 
325
#include "threadmodel.moc"