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

messagelist

  • sources
  • kde-4.14
  • kdepim
  • messagelist
  • core
model.cpp
Go to the documentation of this file.
1 /******************************************************************************
2  *
3  * Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  *******************************************************************************/
20 
21 //
22 // This class is a rather huge monster. It's something that resembles a QAbstractItemModel
23 // (because it has to provide the interface for a QTreeView) but isn't entirely one
24 // (for optimization reasons). It basically manages a tree of items of two types:
25 // GroupHeaderItem and MessageItem. Be sure to read the docs for ViewItemJob.
26 //
27 // A huge credit here goes to Till Adam which seems to have written most
28 // (if not all) of the original KMail threading code. The KMHeaders implementation,
29 // the documentation and his clever ideas were my starting points and essential tools.
30 // This is why I'm adding his copyright entry (copied from headeritem.cpp) here even if
31 // he didn't write a byte in this file until now :)
32 //
33 // Szymon Tomasz Stefanek, 03 Aug 2008 04:50 (am)
34 //
35 // This class contains ideas from:
36 //
37 // kmheaders.cpp / kmheaders.h, headeritem.cpp / headeritem.h
38 // Copyright: (c) 2004 Till Adam < adam at kde dot org >
39 //
40 #include <config-messagelist.h>
41 #include "core/model.h"
42 #include "core/model_p.h"
43 #include "core/view.h"
44 #include "core/filter.h"
45 #include "core/groupheaderitem.h"
46 #include "core/item_p.h"
47 #include "core/messageitem.h"
48 #include "core/modelinvariantrowmapper.h"
49 #include "core/storagemodelbase.h"
50 #include "core/theme.h"
51 #include "core/delegate.h"
52 #include "core/manager.h"
53 #include "core/messageitemsetmanager.h"
54 #include "core/messageitem.h"
55 
56 #include <akonadi/item.h>
57 #include <akonadi/kmime/messagestatus.h>
58 #include "messagecore/utils/stringutil.h"
59 
60 #include <QApplication>
61 #include <QTimer>
62 #include <QDateTime>
63 #include <QScrollBar>
64 
65 #include <KLocalizedString>
66 #include <KCalendarSystem>
67 #include <KGlobal>
68 #include <KIcon>
69 #include <KDebug>
70 
71 namespace MessageList
72 {
73 
74 namespace Core
75 {
76 
77 K_GLOBAL_STATIC( QTimer, _k_heartBeatTimer )
78 
79 
155 class ViewItemJob
156 {
157  public:
158  enum Pass
159  {
160  Pass1Fill = 0,
161  Pass1Cleanup = 1,
162  Pass1Update = 2,
163  Pass2 = 3,
164  Pass3 = 4,
165  Pass4 = 5,
166  Pass5 = 6,
167  LastIndex = 7
168  };
169  private:
170  // Data for "View Fill" jobs
171  int mStartIndex;
172  int mCurrentIndex;
173  int mEndIndex;
174 
175  // Data for "View Cleanup" jobs
176  QList< ModelInvariantIndex * > * mInvariantIndexList;
177 
178  // Common data
179 
180  // The maximum time that we can spend "at once" inside viewItemJobStep() (milliseconds)
181  // The bigger this value, the larger chunks of work we do at once and less the time
182  // we loose in "breaking and resuming" the job. On the other side large values tend
183  // to make the view less responsive up to a "freeze" perception if this value is larger
184  // than 2000.
185  int mChunkTimeout;
186 
187  // The interval between two fillView steps. The larger the interval, the more interactivity
188  // we have. The shorter the interval the more work we get done per second.
189  int mIdleInterval;
190 
191  // The minimum number of messages we process in every viewItemJobStep() call
192  // The larger this value the less time we loose in checking the timeout every N messages.
193  // On the other side, making this very large may make the view less responsive
194  // if we're processing very few messages at a time and very high values (say > 10000) may
195  // eventually make our job unbreakable until the end.
196  int mMessageCheckCount;
197  Pass mCurrentPass;
198 
199  // If this parameter is true then this job uses a "disconnected" UI.
200  // It's FAR faster since we don't need to call beginInsertRows()/endInsertRows()
201  // and we simply emit a layoutChanged() at the end. It can be done only as the first
202  // job though: subsequent jobs can't use layoutChanged() as it looses the expanded
203  // state of items.
204  bool mDisconnectUI;
205  public:
209  ViewItemJob( int startIndex, int endIndex, int chunkTimeout, int idleInterval, int messageCheckCount, bool disconnectUI = false )
210  : mStartIndex( startIndex ), mCurrentIndex( startIndex ), mEndIndex( endIndex ),
211  mInvariantIndexList( 0 ),
212  mChunkTimeout( chunkTimeout ), mIdleInterval( idleInterval ),
213  mMessageCheckCount( messageCheckCount ), mCurrentPass( Pass1Fill ),
214  mDisconnectUI( disconnectUI ) {}
215 
219  ViewItemJob( Pass pass, QList< ModelInvariantIndex * > * invariantIndexList, int chunkTimeout, int idleInterval, int messageCheckCount )
220  : mStartIndex( 0 ), mCurrentIndex( 0 ), mEndIndex( invariantIndexList->count() - 1 ),
221  mInvariantIndexList( invariantIndexList ),
222  mChunkTimeout( chunkTimeout ), mIdleInterval( idleInterval ),
223  mMessageCheckCount( messageCheckCount ), mCurrentPass( pass ),
224  mDisconnectUI( false ) {}
225 
226  ~ViewItemJob()
227  {
228  delete mInvariantIndexList;
229  }
230  public:
231  int startIndex() const
232  { return mStartIndex; }
233  void setStartIndex( int startIndex )
234  { mStartIndex = startIndex; mCurrentIndex = startIndex; }
235  int currentIndex() const
236  { return mCurrentIndex; }
237  void setCurrentIndex( int currentIndex )
238  { mCurrentIndex = currentIndex; }
239  int endIndex() const
240  { return mEndIndex; }
241  void setEndIndex( int endIndex )
242  { mEndIndex = endIndex; }
243  Pass currentPass() const
244  { return mCurrentPass; }
245  void setCurrentPass( Pass pass )
246  { mCurrentPass = pass; }
247  int idleInterval() const
248  { return mIdleInterval; }
249  int chunkTimeout() const
250  { return mChunkTimeout; }
251  int messageCheckCount() const
252  { return mMessageCheckCount; }
253  QList< ModelInvariantIndex * > * invariantIndexList() const
254  { return mInvariantIndexList; }
255  bool disconnectUI() const
256  { return mDisconnectUI; }
257 };
258 
259 } // namespace Core
260 
261 } // namespace MessageList
262 
263 using namespace MessageList::Core;
264 
265 Model::Model( View *pParent )
266  : QAbstractItemModel( pParent ), d( new ModelPrivate( this ) )
267 {
268  d->mRecursionCounterForReset = 0;
269  d->mStorageModel = 0;
270  d->mView = pParent;
271  d->mAggregation = 0;
272  d->mTheme = 0;
273  d->mSortOrder = 0;
274  d->mFilter = 0;
275  d->mPersistentSetManager = 0;
276  d->mInLengthyJobBatch = false;
277  d->mLastSelectedMessageInFolder = 0;
278  d->mLoading = false;
279 
280  d->mRootItem = new Item( Item::InvisibleRoot );
281  d->mRootItem->setViewable( 0, true );
282 
283  d->mFillStepTimer.setSingleShot( true );
284  d->mInvariantRowMapper = new ModelInvariantRowMapper();
285  d->mModelForItemFunctions= this;
286  connect( &d->mFillStepTimer, SIGNAL(timeout()),
287  SLOT(viewItemJobStep()) );
288 
289  d->mCachedTodayLabel = i18n( "Today" );
290  d->mCachedYesterdayLabel = i18n( "Yesterday" );
291  d->mCachedUnknownLabel = i18nc( "Unknown date",
292  "Unknown" );
293  d->mCachedLastWeekLabel = i18n( "Last Week" );
294  d->mCachedTwoWeeksAgoLabel = i18n( "Two Weeks Ago" );
295  d->mCachedThreeWeeksAgoLabel = i18n( "Three Weeks Ago" );
296  d->mCachedFourWeeksAgoLabel = i18n( "Four Weeks Ago" );
297  d->mCachedFiveWeeksAgoLabel = i18n( "Five Weeks Ago" );
298 
299  d->mCachedWatchedOrIgnoredStatusBits = Akonadi::MessageStatus::statusIgnored().toQInt32() | Akonadi::MessageStatus::statusWatched().toQInt32();
300 
301 
302  connect( _k_heartBeatTimer, SIGNAL(timeout()),
303  this, SLOT(checkIfDateChanged()) );
304 
305  if ( !_k_heartBeatTimer->isActive() ) { // First model starts it
306  _k_heartBeatTimer->start( 60000 ); // 1 minute
307  }
308 }
309 
310 Model::~Model()
311 {
312  setStorageModel( 0 );
313 
314  d->clearJobList();
315  d->mOldestItem = 0;
316  d->mNewestItem = 0;
317  d->clearUnassignedMessageLists();
318  d->clearOrphanChildrenHash();
319  d->clearThreadingCacheMessageSubjectMD5ToMessageItem();
320  delete d->mPersistentSetManager;
321  // Delete the invariant row mapper before removing the items.
322  // It's faster since the items will not need to call the invariant
323  delete d->mInvariantRowMapper;
324  delete d->mRootItem;
325 
326  delete d;
327 }
328 
329 void Model::setAggregation( const Aggregation * aggregation )
330 {
331  d->mAggregation = aggregation;
332  d->mView->setRootIsDecorated( ( d->mAggregation->grouping() == Aggregation::NoGrouping ) &&
333  ( d->mAggregation->threading() != Aggregation::NoThreading ) );
334 }
335 
336 void Model::setTheme( const Theme * theme )
337 {
338  d->mTheme = theme;
339 }
340 
341 void Model::setSortOrder( const SortOrder * sortOrder )
342 {
343  d->mSortOrder = sortOrder;
344 }
345 
346 const SortOrder * Model::sortOrder() const
347 {
348  return d->mSortOrder;
349 }
350 
351 void Model::setFilter( const Filter *filter )
352 {
353  d->mFilter = filter;
354 
355  if (d->mFilter)
356  connect( d->mFilter, SIGNAL(finished()), this, SLOT(slotApplyFilter()) );
357 
358  d->slotApplyFilter();
359 }
360 
361 void ModelPrivate::slotApplyFilter()
362 {
363  QList< Item * > * childList = mRootItem->childItems();
364  if ( !childList )
365  return;
366 
367  QModelIndex idx; // invalid
368 
369  QApplication::setOverrideCursor( Qt::WaitCursor );
370  QList< Item * >::ConstIterator end = childList->constEnd();
371  for ( QList< Item * >::ConstIterator it = childList->constBegin(); it != end; ++it )
372  applyFilterToSubtree( *it, idx );
373 
374  QApplication::restoreOverrideCursor();
375 }
376 
377 bool ModelPrivate::applyFilterToSubtree( Item * item, const QModelIndex &parentIndex )
378 {
379  // This function applies the current filter (eventually empty)
380  // to a message tree starting at "item".
381 
382  if ( !mModelForItemFunctions ) {
383  kWarning() << "Cannot apply filter, the UI must be not disconnected.";
384  return true;
385  }
386  Q_ASSERT( item ); // the item must obviously be valid
387  Q_ASSERT( item->isViewable() ); // the item must be viewable
388 
389  // Apply to children first
390 
391  QList< Item * > * childList = item->childItems();
392 
393  bool childrenMatch = false;
394 
395  QModelIndex thisIndex = q->index( item, 0 );
396 
397  if ( childList )
398  {
399  QList< Item * >::ConstIterator end( childList->constEnd() );
400  for ( QList< Item * >::ConstIterator it = childList->constBegin(); it != end; ++it )
401  {
402  if ( applyFilterToSubtree( *it, thisIndex ) )
403  childrenMatch = true;
404  }
405  }
406 
407  if ( !mFilter ) // empty filter always matches (but does not expand items)
408  {
409  mView->setRowHidden( thisIndex.row(), parentIndex, false );
410  return true;
411  }
412 
413  if ( childrenMatch )
414  {
415  mView->setRowHidden( thisIndex.row(), parentIndex, false );
416 
417  if ( !mView->isExpanded( thisIndex ) )
418  mView->expand( thisIndex );
419  return true;
420  }
421 
422  if ( item->type() == Item::Message )
423  {
424  if ( mFilter->match( ( MessageItem * )item ) )
425  {
426  mView->setRowHidden( thisIndex.row(), parentIndex, false );
427  return true;
428  }
429  } // else this is a group header and it never explicitly matches
430 
431  // filter doesn't match, hide the item
432  mView->setRowHidden( thisIndex.row(), parentIndex, true );
433 
434  return false;
435 }
436 
437 int Model::columnCount( const QModelIndex & parent ) const
438 {
439  if ( !d->mTheme )
440  return 0;
441  if ( parent.column() > 0 )
442  return 0;
443  return d->mTheme->columns().count();
444 }
445 
446 QVariant Model::data( const QModelIndex & index, int role ) const
447 {
452 
453  Item* item = static_cast< Item* >( index.internalPointer() );
454 
455  switch( role ) {
457  case Qt::UserRole + 1: //EntityTreeModel::ItemIdRole
458  if( item->type() == MessageList::Core::Item::Message ) {
459  MessageItem* mItem = static_cast<MessageItem*>( item );
460  return QVariant::fromValue( mItem->akonadiItem().id() );
461  } else
462  return QVariant();
463  break;
464  case Qt::UserRole + 2: //EntityTreeModel::ItemRole
465  if( item->type() == MessageList::Core::Item::Message ) {
466  MessageItem* mItem = static_cast<MessageItem*>( item );
467  return QVariant::fromValue( mItem->akonadiItem() );
468  } else
469  return QVariant();
470  break;
471  case Qt::UserRole + 3: //EntityTreeModel::MimeTypeRole
472  if( item->type() == MessageList::Core::Item::Message )
473  return QLatin1String( "message/rfc822" );
474  else
475  return QVariant();
476  break;
477  case Qt::AccessibleTextRole:
478  if( item->type() == MessageList::Core::Item::Message ) {
479  MessageItem* mItem = static_cast<MessageItem*>( item );
480  return mItem->accessibleText( d->mTheme , index.column() );
481  } else if ( item->type() == MessageList::Core::Item::GroupHeader ) {
482  if ( index.column() > 0)
483  return QString();
484  GroupHeaderItem* hItem = static_cast<GroupHeaderItem*>( item );
485  return hItem->label();
486  }
487  return QString();
488  break;
489  default:
490  return QVariant();
491  }
492 }
493 
494 QVariant Model::headerData(int section, Qt::Orientation, int role) const
495 {
496  if ( !d->mTheme )
497  return QVariant();
498 
499  Theme::Column * column = d->mTheme->column( section );
500  if ( !column )
501  return QVariant();
502 
503  if ( d->mStorageModel && column->isSenderOrReceiver() &&
504  ( role == Qt::DisplayRole ) )
505  {
506  if ( d->mStorageModelContainsOutboundMessages )
507  return QVariant( i18n( "Receiver" ) );
508  return QVariant( i18n( "Sender" ) );
509  }
510 
511  const bool columnPixmapEmpty(column->pixmapName().isEmpty());
512  if ( ( role == Qt::DisplayRole ) && columnPixmapEmpty )
513  return QVariant( column->label() );
514  else if ( ( role == Qt::ToolTipRole ) && !columnPixmapEmpty )
515  return QVariant( column->label() );
516  else if ( ( role == Qt::DecorationRole ) && !columnPixmapEmpty )
517  return QVariant( KIcon( column->pixmapName() ) );
518 
519  return QVariant();
520 }
521 
522 QModelIndex Model::index( Item *item, int column ) const
523 {
524  if ( !d->mModelForItemFunctions )
525  return QModelIndex(); // called with disconnected UI: the item isn't known on the Qt side, yet
526 
527  if ( !item ) {
528  return QModelIndex();
529  }
530  // FIXME: This function is a bottleneck (the caching in indexOfChildItem only works 30% of the time)
531  Item * par = item->parent();
532  if ( !par )
533  {
534  if ( item != d->mRootItem )
535  item->dump(QString());
536  return QModelIndex();
537  }
538 
539  const int index = par->indexOfChildItem(item);
540  if ( index < 0 )
541  return QModelIndex(); // BUG
542  return createIndex(index, column, item);
543 }
544 
545 QModelIndex Model::index( int row, int column, const QModelIndex &parent ) const
546 {
547  if ( !d->mModelForItemFunctions )
548  return QModelIndex(); // called with disconnected UI: the item isn't known on the Qt side, yet
549 
550 #ifdef READD_THIS_IF_YOU_WANT_TO_PASS_MODEL_TEST
551  if ( column < 0 )
552  return QModelIndex(); // senseless column (we could optimize by skipping this check but ModelTest from trolltech is pedantic)
553 #endif
554 
555  const Item *item;
556  if ( parent.isValid() )
557  {
558  item = static_cast< const Item * >( parent.internalPointer() );
559  if ( !item )
560  return QModelIndex(); // should never happen
561  } else {
562  item = d->mRootItem;
563  }
564 
565  if ( parent.column() > 0 )
566  return QModelIndex(); // parent column is not 0: shouldn't have children (as per Qt documentation)
567 
568  Item * child = item->childItem( row );
569  if ( !child )
570  return QModelIndex(); // no such row in parent
571  return createIndex( row, column, child );
572 }
573 
574 QModelIndex Model::parent( const QModelIndex &modelIndex ) const
575 {
576  Q_ASSERT( d->mModelForItemFunctions ); // should be never called with disconnected UI
577 
578  if ( !modelIndex.isValid() )
579  return QModelIndex(); // should never happen
580  Item *item = static_cast< Item * >( modelIndex.internalPointer() );
581  if ( !item )
582  return QModelIndex();
583  Item *par = item->parent();
584  if ( !par )
585  return QModelIndex(); // should never happen
586  //return index( par, modelIndex.column() );
587  return index( par, 0 ); // parents are always in column 0 (as per Qt documentation)
588 }
589 
590 int Model::rowCount( const QModelIndex &parent ) const
591 {
592  if ( !d->mModelForItemFunctions )
593  return 0; // called with disconnected UI
594 
595  const Item *item;
596  if ( parent.isValid() )
597  {
598  item = static_cast< const Item * >( parent.internalPointer() );
599  if ( !item )
600  return 0; // should never happen
601  } else {
602  item = d->mRootItem;
603  }
604 
605  if ( !item->isViewable() )
606  return 0;
607 
608  return item->childItemCount();
609 }
610 
611 class RecursionPreventer
612 {
613 public:
614  RecursionPreventer( int &counter )
615  : mCounter( counter ) { mCounter++; }
616  ~RecursionPreventer() { mCounter--; }
617  bool isRecursive() const { return mCounter > 1; }
618 
619 private:
620  int &mCounter;
621 };
622 
623 StorageModel *Model::storageModel() const
624 {
625  return d->mStorageModel;
626 }
627 
628 void ModelPrivate::clear()
629 {
630  if( mFillStepTimer.isActive() )
631  mFillStepTimer.stop();
632 
633  // Kill pre-selection at this stage
634  mPreSelectionMode = PreSelectNone;
635  mLastSelectedMessageInFolder = 0;
636  mOldestItem = 0;
637  mNewestItem = 0;
638 
639  // Reset the row mapper before removing items
640  // This is faster since the items don't need to access the mapper.
641  mInvariantRowMapper->modelReset();
642 
643  clearJobList();
644  clearUnassignedMessageLists();
645  clearOrphanChildrenHash();
646  mGroupHeaderItemHash.clear();
647  mGroupHeadersThatNeedUpdate.clear();
648  mThreadingCacheMessageIdMD5ToMessageItem.clear();
649  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.clear();
650  clearThreadingCacheMessageSubjectMD5ToMessageItem();
651  mViewItemJobStepChunkTimeout = 100;
652  mViewItemJobStepIdleInterval = 10;
653  mViewItemJobStepMessageCheckCount = 10;
654  delete mPersistentSetManager;
655  mPersistentSetManager = 0;
656  mCurrentItemToRestoreAfterViewItemJobStep = 0;
657 
658  mTodayDate = QDate::currentDate();
659 
660  // FIXME: CLEAR THE FILTER HERE AS WE CAN'T APPLY IT WITH UI DISCONNECTED!
661 
662 
663  mRootItem->killAllChildItems();
664 
665  q->reset();
666  //emit headerDataChanged();
667 
668  mView->modelHasBeenReset();
669  mView->selectionModel()->clearSelection();
670 }
671 
672 void Model::setStorageModel( StorageModel *storageModel, PreSelectionMode preSelectionMode )
673 {
674  // Prevent a case of recursion when opening a folder that has a message and the folder was
675  // never opened before.
676  RecursionPreventer preventer( d->mRecursionCounterForReset );
677  if ( preventer.isRecursive() )
678  return;
679 
680  d->clear();
681 
682  if ( d->mStorageModel ) {
683  disconnect( d->mStorageModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
684  this, SLOT(slotStorageModelRowsInserted(QModelIndex,int,int)) );
685  disconnect( d->mStorageModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
686  this, SLOT(slotStorageModelRowsRemoved(QModelIndex,int,int)) );
687 
688  disconnect( d->mStorageModel, SIGNAL(layoutChanged()),
689  this, SLOT(slotStorageModelLayoutChanged()) );
690  disconnect( d->mStorageModel, SIGNAL(modelReset()),
691  this, SLOT(slotStorageModelLayoutChanged()) );
692 
693  disconnect( d->mStorageModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
694  this, SLOT(slotStorageModelDataChanged(QModelIndex,QModelIndex)) );
695  disconnect( d->mStorageModel, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
696  this, SLOT(slotStorageModelHeaderDataChanged(Qt::Orientation,int,int)) );
697  }
698 
699  d->mStorageModel = storageModel;
700 
701  if ( !d->mStorageModel )
702  return; // no folder: nothing to fill
703 
704  // Sometimes the folders need to be resurrected...
705  d->mStorageModel->prepareForScan();
706 
707  d->mPreSelectionMode = preSelectionMode;
708  d->mStorageModelContainsOutboundMessages = d->mStorageModel->containsOutboundMessages();
709 
710  connect( d->mStorageModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
711  this, SLOT(slotStorageModelRowsInserted(QModelIndex,int,int)) );
712  connect( d->mStorageModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
713  this, SLOT(slotStorageModelRowsRemoved(QModelIndex,int,int)) );
714 
715  connect( d->mStorageModel, SIGNAL(layoutChanged()),
716  this, SLOT(slotStorageModelLayoutChanged()) );
717  connect( d->mStorageModel, SIGNAL(modelReset()),
718  this, SLOT(slotStorageModelLayoutChanged()) );
719 
720  connect( d->mStorageModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
721  this, SLOT(slotStorageModelDataChanged(QModelIndex,QModelIndex)) );
722  connect( d->mStorageModel, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
723  this, SLOT(slotStorageModelHeaderDataChanged(Qt::Orientation,int,int)) );
724 
725  if ( d->mStorageModel->rowCount() == 0 )
726  return; // folder empty: nothing to fill
727 
728  // Here we use different strategies based on user preference and the folder size.
729  // The knobs we can tune are:
730  //
731  // - The number of jobs used to scan the whole folder and their order
732  //
733  // There are basically two approaches to this. One is the "single big job"
734  // approach. It scans the folder from the beginning to the end in a single job
735  // entry. The job passes are done only once. It's advantage is that it's simplier
736  // and it's less likely to generate imperfect parent threadings. The bad
737  // side is that since the folders are "sort of" date ordered then the most interesting
738  // messages show up at the end of the work. Not nice for large folders.
739  // The other approach uses two jobs. This is a bit slower but smarter strategy.
740  // First we scan the latest 1000 messages and *then* take care of the older ones.
741  // This will show up the most interesting messages almost immediately. (Well...
742  // All this assuming that the underlying storage always appends the newly arrived messages)
743  // The strategy is slower since it generates some imperfect parent threadings which must be
744  // adjusted by the second job. For instance, in my kernel mailing list folder this "smart" approach
745  // generates about 150 additional imperfectly threaded children... but the "today"
746  // messages show up almost immediately. The two-chunk job also makes computing
747  // the percentage user feedback a little harder and might break some optimization
748  // in the insertions (we're able to optimize appends and prepends but a chunked
749  // job is likely to split our work at a boundary where messages are always inserted
750  // in the middle of the list).
751  //
752  // - The maximum time to spend inside a single job step
753  //
754  // The larger this time, the greater the number of messages per second that this
755  // engine can process but also greater time with frozen UI -> less interactivity.
756  // Reasonable values start at 50 msecs. Values larger than 300 msecs are very likely
757  // to be percieved by the user as UI non-reactivity.
758  //
759  // - The number of messages processed in each job step subchunk.
760  //
761  // A job subchunk is processed without checking the maximum time above. This means
762  // that each job step will process at least the number of messages specified by this value.
763  // Very low values mean that we respect the maximum time very carefully but we also
764  // waste time to check if we ran out of time :)
765  // Very high values are likely to cause the engine to not respect the maximum step time.
766  // Reasonable values go from 5 to 100.
767  //
768  // - The "idle" time between two steps
769  //
770  // The lower this time, the greater the number of messages per second that this
771  // engine can process but also lower time for the UI to process events -> less interactivity.
772  // A value of 0 here means that Qt will trigger the timer as soon as it has some
773  // idle time to spend. UI events will be still processed but slowdowns are possible.
774  // 0 is reasonable though. Values larger than 200 will tend to make the total job
775  // completion times high.
776  //
777 
778  // If we have no filter it seems that we can apply a huge optimization.
779  // We disconnect the UI for the first huge filling job. This allows us
780  // to save the extremely expensive beginInsertRows()/endInsertRows() calls
781  // and call a single layoutChanged() at the end. This slows down a lot item
782  // expansion. But on the other side if only few items need to be expanded
783  // then this strategy is better. If filtering is enabled then this strategy
784  // isn't applicable (because filtering requires interaction with the UI
785  // while the data is loading).
786 
787  // So...
788 
789  // For the very first small chunk it's ok to work with disconnected UI as long
790  // as we have no filter. The first small chunk is always 1000 messages, so
791  // even if all of them are expanded, it's still somewhat acceptable.
792  bool canDoFirstSmallChunkWithDisconnectedUI = !d->mFilter;
793 
794  // Larger works need a bigger condition: few messages must be expanded in the end.
795  bool canDoJobWithDisconnectedUI =
796  // we have no filter
797  !d->mFilter &&
798  (
799  // we do no threading at all
800  ( d->mAggregation->threading() == Aggregation::NoThreading ) ||
801  // or we never expand threads
802  ( d->mAggregation->threadExpandPolicy() == Aggregation::NeverExpandThreads ) ||
803  // or we expand threads but we'll be going to expand really only a few
804  (
805  // so we don't expand them all
806  ( d->mAggregation->threadExpandPolicy() != Aggregation::AlwaysExpandThreads ) &&
807  // and we'd expand only a few in fact
808  ( d->mStorageModel->initialUnreadRowCountGuess() < 1000 )
809  )
810  );
811 
812  switch ( d->mAggregation->fillViewStrategy() )
813  {
814  case Aggregation::FavorInteractivity:
815  // favor interactivity
816  if ( ( !canDoJobWithDisconnectedUI ) && ( d->mStorageModel->rowCount() > 3000 ) ) // empiric value
817  {
818  // First a small job with the most recent messages. Large chunk, small (but non zero) idle interval
819  // and a larger number of messages to process at once.
820  ViewItemJob * job1 = new ViewItemJob( d->mStorageModel->rowCount() - 1000, d->mStorageModel->rowCount() - 1, 200, 20, 100, canDoFirstSmallChunkWithDisconnectedUI );
821  d->mViewItemJobs.append( job1 );
822  // Then a larger job with older messages. Small chunk, bigger idle interval, small number of messages to
823  // process at once.
824  ViewItemJob * job2 = new ViewItemJob( 0, d->mStorageModel->rowCount() - 1001, 100, 50, 10, false );
825  d->mViewItemJobs.append( job2 );
826 
827  // We could even extremize this by splitting the folder in several
828  // chunks and scanning them from the newest to the oldest... but the overhead
829  // due to imperfectly threaded children would be probably too big.
830  } else {
831  // small folder or can be done with disconnected UI: single chunk work.
832  // Lag the CPU a bit more but not too much to destroy even the earliest interactivity.
833  ViewItemJob * job = new ViewItemJob( 0, d->mStorageModel->rowCount() - 1, 150, 30, 30, canDoJobWithDisconnectedUI );
834  d->mViewItemJobs.append( job );
835  }
836  break;
837  case Aggregation::FavorSpeed:
838  // More batchy jobs, still interactive to a certain degree
839  if ( ( !canDoJobWithDisconnectedUI ) && ( d->mStorageModel->rowCount() > 3000 ) ) // empiric value
840  {
841  // large folder, but favor speed
842  ViewItemJob * job1 = new ViewItemJob( d->mStorageModel->rowCount() - 1000, d->mStorageModel->rowCount() - 1, 250, 0, 100, canDoFirstSmallChunkWithDisconnectedUI );
843  d->mViewItemJobs.append( job1 );
844  ViewItemJob * job2 = new ViewItemJob( 0, d->mStorageModel->rowCount() - 1001, 200, 0, 10, false );
845  d->mViewItemJobs.append( job2 );
846  } else {
847  // small folder or can be done with disconnected UI and favor speed: single chunk work.
848  // Lag the CPU more, get more work done
849  ViewItemJob * job = new ViewItemJob( 0, d->mStorageModel->rowCount() - 1, 250, 0, 100, canDoJobWithDisconnectedUI );
850  d->mViewItemJobs.append( job );
851  }
852  break;
853  case Aggregation::BatchNoInteractivity:
854  {
855  // one large job, never interrupt, block UI
856  ViewItemJob * job = new ViewItemJob( 0, d->mStorageModel->rowCount() - 1, 60000, 0, 100000, canDoJobWithDisconnectedUI );
857  d->mViewItemJobs.append( job );
858  }
859  break;
860  default:
861  kWarning() << "Unrecognized fill view strategy";
862  Q_ASSERT( false );
863  break;
864  }
865 
866  d->mLoading = true;
867 
868  d->viewItemJobStep();
869 }
870 
871 void ModelPrivate::checkIfDateChanged()
872 {
873  // This function is called by MessageList::Core::Manager once in a while (every 1 minute or sth).
874  // It is used to check if the current date has changed (with respect to mTodayDate).
875  //
876  // Our message items cache the formatted dates (as formatting them
877  // on the fly would be too expensive). We also cache the labels of the groups which often display dates.
878  // When the date changes we would need to fix all these strings.
879  //
880  // A dedicated algorithm to refresh the labels of the items would be either too complex
881  // or would block on large trees. Fixing the labels of the groups is also quite hard...
882  //
883  // So to keep the things simple we just reload the view.
884 
885  if ( !mStorageModel )
886  return; // nothing to do
887 
888  if ( mLoading )
889  return; // not now
890 
891  if ( !mViewItemJobs.isEmpty() )
892  return; // not now
893 
894  if ( mTodayDate == QDate::currentDate() )
895  return; // date not changed
896 
897  // date changed, reload the view (and try to preserve the current selection)
898  q->setStorageModel( mStorageModel, PreSelectLastSelected );
899 }
900 
901 
902 void Model::setPreSelectionMode( PreSelectionMode preSelect )
903 {
904  d->mPreSelectionMode = preSelect;
905  d->mLastSelectedMessageInFolder = 0;
906 }
907 
908 //
909 // The "view fill" algorithm implemented in the functions below is quite smart but also quite complex.
910 // It's governed by the following goals:
911 //
912 // - Be flexible: allow different configurations from "unsorted flat list" to a "grouped and threaded
913 // list with different sorting algorightms applied to each aggregation level"
914 // - Be reasonably fast
915 // - Be non blocking: UI shouldn't freeze while the algorithm is running
916 // - Be interruptible: user must be able to abort the execution and just switch to another folder in the middle
917 //
918 
919 void ModelPrivate::clearUnassignedMessageLists()
920 {
921  // This is a bit tricky...
922  // The three unassigned message lists contain messages that have been created
923  // but not yet attached to the view. There may be two major cases for a message:
924  // - it has no parent -> it must be deleted and it will delete its children too
925  // - it has a parent -> it must NOT be deleted since it will be deleted by its parent.
926 
927  // Sometimes the things get a little complicated since in Pass2 and Pass3
928  // we have transitional states in that the MessageItem object can be in two of these lists.
929 
930  // WARNING: This function does NOT fixup mNewestItem and mOldestItem. If one of these
931  // two messages is in the lists below, it's deleted and the member becomes a dangling pointer.
932  // The caller must ensure that both mNewestItem and mOldestItem are set to 0
933  // and this is enforced in the assert below to avoid errors. This basically means
934  // that this function should be called only when the storage model changes or
935  // when the model is destroyed.
936  Q_ASSERT( ( mOldestItem == 0 ) && ( mNewestItem == 0 ) );
937 
938  QList< MessageItem * >::ConstIterator it;
939 
940  if ( !mUnassignedMessageListForPass2.isEmpty() )
941  {
942  // We're actually in Pass1* or Pass2: everything is mUnassignedMessageListForPass2
943  // Something may *also* be in mUnassignedMessageListForPass3 and mUnassignedMessageListForPass4
944  // but that are duplicates for sure.
945 
946  // We can't just sweep the list and delete parentless items since each delete
947  // could kill children which are somewhere AFTER in the list: accessing the children
948  // would then lead to a SIGSEGV. We first sweep the list gathering parentless
949  // items and *then* delete them without accessing the parented ones.
950 
951  QList< MessageItem * > parentless;
952  QList< MessageItem * >::ConstIterator end( mUnassignedMessageListForPass2.constEnd() );
953 
954  for ( it = mUnassignedMessageListForPass2.constBegin();
955  it != end; ++it )
956  {
957  if( !( *it )->parent() )
958  parentless.append( *it );
959  }
960 
961  end = parentless.constEnd();
962  for ( it = parentless.constBegin(); it != end; ++it )
963  delete *it;
964 
965  mUnassignedMessageListForPass2.clear();
966  // Any message these list contain was also in mUnassignedMessageListForPass2
967  mUnassignedMessageListForPass3.clear();
968  mUnassignedMessageListForPass4.clear();
969  return;
970  }
971 
972  // mUnassignedMessageListForPass2 is empty
973 
974  if ( !mUnassignedMessageListForPass3.isEmpty() )
975  {
976  // We're actually at the very end of Pass2 or inside Pass3
977  // Pass2 pushes stuff in mUnassignedMessageListForPass3 *or* mUnassignedMessageListForPass4
978  // Pass3 pushes stuff from mUnassignedMessageListForPass3 to mUnassignedMessageListForPass4
979  // So if we're in Pass2 then the two lists contain distinct messages but if we're in Pass3
980  // then the two lists may contain the same messages.
981 
982  if ( !mUnassignedMessageListForPass4.isEmpty() )
983  {
984  // We're actually in Pass3: the messiest one.
985 
986  QHash< MessageItem *, MessageItem * > itemsToDelete;
987  QList< MessageItem * >::ConstIterator end( mUnassignedMessageListForPass3.constEnd() );
988 
989  for ( it = mUnassignedMessageListForPass3.constBegin(); it != end; ++it )
990  {
991  if( !( *it )->parent() )
992  itemsToDelete.insert( *it, *it );
993  }
994  end = mUnassignedMessageListForPass4.constEnd();
995  for ( it = mUnassignedMessageListForPass4.constBegin(); it != end; ++it )
996  {
997  if( !( *it )->parent() )
998  itemsToDelete.insert( *it, *it );
999  }
1000  QHash< MessageItem *, MessageItem * >::ConstIterator end3 = itemsToDelete.constEnd();
1001  for ( QHash< MessageItem *, MessageItem * >::ConstIterator it3 = itemsToDelete.constBegin(); it3 != end3; ++it3 )
1002  delete ( *it3 );
1003 
1004  mUnassignedMessageListForPass3.clear();
1005  mUnassignedMessageListForPass4.clear();
1006  return;
1007  }
1008 
1009  // mUnassignedMessageListForPass4 is empty so we must be at the end of a very special kind of Pass2
1010  // We have the same problem as in mUnassignedMessageListForPass2.
1011  QList< MessageItem * > parentless;
1012  QList< MessageItem * >::ConstIterator end = mUnassignedMessageListForPass3.constEnd();
1013  for ( it = mUnassignedMessageListForPass3.constBegin(); it != end; ++it )
1014  {
1015  if( !( *it )->parent() )
1016  parentless.append( *it );
1017  }
1018  end = parentless.constEnd();
1019  for ( it = parentless.constBegin(); it != end; ++it )
1020  delete *it;
1021 
1022  mUnassignedMessageListForPass3.clear();
1023  return;
1024  }
1025 
1026  // mUnassignedMessageListForPass3 is empty
1027  if ( !mUnassignedMessageListForPass4.isEmpty() )
1028  {
1029  // we're in Pass4.. this is easy.
1030 
1031  // We have the same problem as in mUnassignedMessageListForPass2.
1032  QList< MessageItem * > parentless;
1033  QList< MessageItem * >::ConstIterator end = mUnassignedMessageListForPass4.constEnd();
1034  for ( it = mUnassignedMessageListForPass4.constBegin(); it != end; ++it )
1035  {
1036  if( !( *it )->parent() )
1037  parentless.append( *it );
1038  }
1039  end = parentless.constEnd();
1040  for ( it = parentless.constBegin(); it != end; ++it )
1041  delete *it;
1042 
1043  mUnassignedMessageListForPass4.clear();
1044  return;
1045  }
1046 }
1047 
1048 void ModelPrivate::clearThreadingCacheMessageSubjectMD5ToMessageItem()
1049 {
1050  qDeleteAll( mThreadingCacheMessageSubjectMD5ToMessageItem );
1051  mThreadingCacheMessageSubjectMD5ToMessageItem.clear();
1052 }
1053 
1054 void ModelPrivate::clearOrphanChildrenHash()
1055 {
1056  QHash< MessageItem *, MessageItem * >::ConstIterator end( mOrphanChildrenHash.constEnd() );
1057  for ( QHash< MessageItem *, MessageItem * >::ConstIterator it = mOrphanChildrenHash.constBegin();
1058  it != end; ++it )
1059  {
1060  //Q_ASSERT( !( *it )->parent() ); <-- this assert can actually fail for items that get a temporary parent assigned (to preserve the selection).
1061  delete ( *it );
1062  }
1063  mOrphanChildrenHash.clear();
1064 }
1065 
1066 void ModelPrivate::clearJobList()
1067 {
1068  if ( mViewItemJobs.isEmpty() )
1069  return;
1070 
1071  if ( mInLengthyJobBatch )
1072  {
1073  mInLengthyJobBatch = false;
1074  mView->modelJobBatchTerminated();
1075  }
1076 
1077  QList< ViewItemJob * >::ConstIterator end = mViewItemJobs.constEnd();
1078  for( QList< ViewItemJob * >::ConstIterator it = mViewItemJobs.constBegin();
1079  it != end; ++it )
1080  delete ( *it );
1081  mViewItemJobs.clear();
1082 
1083  mModelForItemFunctions = q; // make sure it's true, as there remains no job with disconnected UI
1084 }
1085 
1086 
1087 void ModelPrivate::attachGroup( GroupHeaderItem *ghi )
1088 {
1089  if ( ghi->parent() )
1090  {
1091  if (
1092  ( ( ghi )->childItemCount() > 0 ) && // has children
1093  ( ghi )->isViewable() && // is actually attached to the viewable root
1094  mModelForItemFunctions && // the UI is not disconnected
1095  mView->isExpanded( q->index( ghi, 0 ) ) // is actually expanded
1096  )
1097  saveExpandedStateOfSubtree( ghi );
1098 
1099  // FIXME: This *WILL* break selection and current index... :/
1100 
1101  ghi->parent()->takeChildItem( mModelForItemFunctions, ghi );
1102  }
1103 
1104  ghi->setParent( mRootItem );
1105 
1106  // I'm using a macro since it does really improve readability.
1107  // I'm NOT using a helper function since gcc will refuse to inline some of
1108  // the calls because they make this function grow too much.
1109 #define INSERT_GROUP_WITH_COMPARATOR( _ItemComparator ) \
1110  switch( mSortOrder->groupSortDirection() ) \
1111  { \
1112  case SortOrder::Ascending: \
1113  mRootItem->d_ptr->insertChildItem< _ItemComparator, true >( mModelForItemFunctions, ghi ); \
1114  break; \
1115  case SortOrder::Descending: \
1116  mRootItem->d_ptr->insertChildItem< _ItemComparator, false >( mModelForItemFunctions, ghi ); \
1117  break; \
1118  default: /* should never happen... */ \
1119  mRootItem->appendChildItem( mModelForItemFunctions, ghi ); \
1120  break; \
1121 }
1122 
1123  switch( mSortOrder->groupSorting() )
1124  {
1125  case SortOrder::SortGroupsByDateTime:
1126  INSERT_GROUP_WITH_COMPARATOR( ItemDateComparator )
1127  break;
1128  case SortOrder::SortGroupsByDateTimeOfMostRecent:
1129  INSERT_GROUP_WITH_COMPARATOR( ItemMaxDateComparator )
1130  break;
1131  case SortOrder::SortGroupsBySenderOrReceiver:
1132  INSERT_GROUP_WITH_COMPARATOR( ItemSenderOrReceiverComparator )
1133  break;
1134  case SortOrder::SortGroupsBySender:
1135  INSERT_GROUP_WITH_COMPARATOR( ItemSenderComparator )
1136  break;
1137  case SortOrder::SortGroupsByReceiver:
1138  INSERT_GROUP_WITH_COMPARATOR( ItemReceiverComparator )
1139  break;
1140  case SortOrder::NoGroupSorting:
1141  mRootItem->appendChildItem( mModelForItemFunctions, ghi );
1142  break;
1143  default: // should never happen
1144  mRootItem->appendChildItem( mModelForItemFunctions, ghi );
1145  break;
1146  }
1147 
1148  if ( ghi->initialExpandStatus() == Item::ExpandNeeded ) // this actually is a "non viewable expanded state"
1149  if ( ghi->childItemCount() > 0 )
1150  if ( mModelForItemFunctions ) // the UI is not disconnected
1151  syncExpandedStateOfSubtree( ghi );
1152 
1153  // A group header is always viewable, when attached: apply the filter, if we have it.
1154  if ( mFilter )
1155  {
1156  Q_ASSERT( mModelForItemFunctions ); // UI must be NOT disconnected
1157  // apply the filter to subtree
1158  applyFilterToSubtree( ghi, QModelIndex() );
1159  }
1160 }
1161 
1162 void ModelPrivate::saveExpandedStateOfSubtree( Item *root )
1163 {
1164  Q_ASSERT( mModelForItemFunctions ); // UI must be NOT disconnected here
1165  Q_ASSERT( root );
1166 
1167  root->setInitialExpandStatus( Item::ExpandNeeded );
1168 
1169  QList< Item * > * children = root->childItems();
1170  if ( !children )
1171  return;
1172  QList< Item * >::ConstIterator end( children->constEnd() );
1173  for( QList< Item * >::ConstIterator it = children->constBegin(); it != end; ++it )
1174  {
1175  if (
1176  ( ( *it )->childItemCount() > 0 ) && // has children
1177  ( *it )->isViewable() && // is actually attached to the viewable root
1178  mView->isExpanded( q->index( *it, 0 ) ) // is actually expanded
1179  )
1180  saveExpandedStateOfSubtree( *it );
1181  }
1182 }
1183 
1184 void ModelPrivate::syncExpandedStateOfSubtree( Item *root )
1185 {
1186  Q_ASSERT( mModelForItemFunctions ); // UI must be NOT disconnected here
1187 
1188  // WE ASSUME that:
1189  // - the item is viewable
1190  // - its initialExpandStatus() is Item::ExpandNeeded
1191  // - it has at least one children (well.. this is not a strict requirement, but it's a waste of resources to expand items that don't have children)
1192 
1193  QModelIndex idx = q->index( root, 0 );
1194 
1195  //if ( !mView->isExpanded( idx ) ) // this is O(logN!) in Qt.... very ugly... but it should never happen here
1196  mView->expand( idx ); // sync the real state in the view
1197  root->setInitialExpandStatus( Item::ExpandExecuted );
1198 
1199  QList< Item * > * children = root->childItems();
1200  if ( !children )
1201  return;
1202 
1203  QList< Item * >::ConstIterator end( children->constEnd() );
1204  for( QList< Item * >::ConstIterator it = children->constBegin(); it != end; ++it )
1205  {
1206  if ( ( *it )->initialExpandStatus() == Item::ExpandNeeded )
1207  {
1208  if ( ( *it )->childItemCount() > 0 )
1209  syncExpandedStateOfSubtree( *it );
1210  }
1211  }
1212 }
1213 
1214 void ModelPrivate::attachMessageToGroupHeader( MessageItem *mi )
1215 {
1216  QString groupLabel;
1217  time_t date;
1218 
1219  // compute the group header label and the date
1220  switch( mAggregation->grouping() )
1221  {
1222  case Aggregation::GroupByDate:
1223  case Aggregation::GroupByDateRange:
1224  {
1225  if ( mAggregation->threadLeader() == Aggregation::MostRecentMessage )
1226  {
1227  date = mi->maxDate();
1228  } else
1229  {
1230  date = mi->date();
1231  }
1232 
1233  QDateTime dt;
1234  dt.setTime_t( date );
1235  QDate dDate = dt.date();
1236  const KCalendarSystem *calendar = KGlobal::locale()->calendar();
1237  int daysAgo = -1;
1238  if ( calendar->isValid( dDate ) && calendar->isValid( mTodayDate ) ) {
1239  daysAgo = dDate.daysTo( mTodayDate );
1240  }
1241 
1242  if ( ( daysAgo < 0 ) || // In the future
1243  ( static_cast< uint >( date ) == static_cast< uint >( -1 ) ) ) // Invalid
1244  {
1245  groupLabel = mCachedUnknownLabel;
1246  } else if( daysAgo == 0 ) // Today
1247  {
1248  groupLabel = mCachedTodayLabel;
1249  } else if ( daysAgo == 1 ) // Yesterday
1250  {
1251  groupLabel = mCachedYesterdayLabel;
1252  } else if ( daysAgo > 1 && daysAgo < calendar->daysInWeek( mTodayDate ) ) // Within last seven days
1253  {
1254  groupLabel = KGlobal::locale()->calendar()->weekDayName( dDate );
1255  } else if ( mAggregation->grouping() == Aggregation::GroupByDate ) { // GroupByDate seven days or more ago
1256  groupLabel = KGlobal::locale()->formatDate( dDate, KLocale::ShortDate );
1257  } else if( ( calendar->month( dDate ) == calendar->month( mTodayDate ) ) && // GroupByDateRange within this month
1258  ( calendar->year( dDate ) == calendar->year( mTodayDate ) ) )
1259  {
1260  int startOfWeekDaysAgo = ( calendar->daysInWeek( mTodayDate ) + calendar->dayOfWeek( mTodayDate ) -
1261  KGlobal::locale()->weekStartDay() ) % calendar->daysInWeek( mTodayDate );
1262  int weeksAgo = ( ( daysAgo - startOfWeekDaysAgo ) / calendar->daysInWeek( mTodayDate ) ) + 1;
1263  switch( weeksAgo )
1264  {
1265  case 0: // This week
1266  groupLabel = KGlobal::locale()->calendar()->weekDayName( dDate );
1267  break;
1268  case 1: // 1 week ago
1269  groupLabel = mCachedLastWeekLabel;
1270  break;
1271  case 2:
1272  groupLabel = mCachedTwoWeeksAgoLabel;
1273  break;
1274  case 3:
1275  groupLabel = mCachedThreeWeeksAgoLabel;
1276  break;
1277  case 4:
1278  groupLabel = mCachedFourWeeksAgoLabel;
1279  break;
1280  case 5:
1281  groupLabel = mCachedFiveWeeksAgoLabel;
1282  break;
1283  default: // should never happen
1284  groupLabel = mCachedUnknownLabel;
1285  }
1286  } else if ( calendar->year( dDate ) == calendar->year( mTodayDate ) ) { // GroupByDateRange within this year
1287  groupLabel = calendar->monthName( dDate );
1288  } else { // GroupByDateRange in previous years
1289  groupLabel = i18nc( "Message Aggregation Group Header: Month name and Year number", "%1 %2", calendar->monthName( dDate ),
1290  calendar->formatDate( dDate, KLocale::Year, KLocale::LongNumber ) );
1291  }
1292  break;
1293  }
1294 
1295  case Aggregation::GroupBySenderOrReceiver:
1296  date = mi->date();
1297  groupLabel = MessageCore::StringUtil::stripEmailAddr( mi->senderOrReceiver() );
1298  break;
1299 
1300  case Aggregation::GroupBySender:
1301  date = mi->date();
1302  groupLabel = MessageCore::StringUtil::stripEmailAddr( mi->sender() );
1303  break;
1304 
1305  case Aggregation::GroupByReceiver:
1306  date = mi->date();
1307  groupLabel = MessageCore::StringUtil::stripEmailAddr( mi->receiver() );
1308  break;
1309 
1310  case Aggregation::NoGrouping:
1311  // append directly to root
1312  attachMessageToParent( mRootItem, mi );
1313  return;
1314 
1315  default:
1316  // should never happen
1317  attachMessageToParent( mRootItem, mi );
1318  return;
1319  }
1320 
1321  GroupHeaderItem * ghi;
1322 
1323  ghi = mGroupHeaderItemHash.value( groupLabel, 0 );
1324  if( !ghi )
1325  {
1326  // not found
1327 
1328  ghi = new GroupHeaderItem( groupLabel );
1329  ghi->initialSetup( date, mi->size(), mi->sender(), mi->receiver(), mi->useReceiver() );
1330 
1331  switch( mAggregation->groupExpandPolicy() )
1332  {
1333  case Aggregation::NeverExpandGroups:
1334  // nothing to do
1335  break;
1336  case Aggregation::AlwaysExpandGroups:
1337  // expand always
1338  ghi->setInitialExpandStatus( Item::ExpandNeeded );
1339  break;
1340  case Aggregation::ExpandRecentGroups:
1341  // expand only if "close" to today
1342  if ( mViewItemJobStepStartTime > ghi->date() )
1343  {
1344  if ( ( mViewItemJobStepStartTime - ghi->date() ) < ( 3600 * 72 ) )
1345  ghi->setInitialExpandStatus( Item::ExpandNeeded );
1346  } else {
1347  if ( ( ghi->date() - mViewItemJobStepStartTime ) < ( 3600 * 72 ) )
1348  ghi->setInitialExpandStatus( Item::ExpandNeeded );
1349  }
1350  break;
1351  default:
1352  // b0rken
1353  break;
1354  }
1355 
1356  attachMessageToParent( ghi, mi );
1357 
1358  attachGroup( ghi ); // this will expand the group if required
1359 
1360  mGroupHeaderItemHash.insert( groupLabel, ghi );
1361  } else {
1362  // the group was already there (certainly viewable)
1363 
1364  // This function may be also called to re-group a message.
1365  // That is, to eventually find a new group for a message that has changed
1366  // its properties (but was already attacched to a group).
1367  // So it may happen that we find out that in fact re-grouping wasn't really
1368  // needed because the message is already in the correct group.
1369  if ( mi->parent() == ghi )
1370  return; // nothing to be done
1371 
1372  attachMessageToParent( ghi, mi );
1373  }
1374 }
1375 
1376 MessageItem * ModelPrivate::findMessageParent( MessageItem * mi )
1377 {
1378  Q_ASSERT( mAggregation->threading() != Aggregation::NoThreading ); // caller must take care of this
1379 
1380  // This function attempts to find a thread parent for the item "mi"
1381  // which actually may already have a children subtree.
1382 
1383  // Forged or plain broken message trees are dangerous here.
1384  // For example, a message tree with circular references like
1385  //
1386  // Message mi, Id=1, In-Reply-To=2
1387  // Message childOfMi, Id=2, In-Reply-To=1
1388  //
1389  // is perfectly possible and will cause us to find childOfMi
1390  // as parent of mi. This will then create a loop in the message tree
1391  // (which will then no longer be a tree in fact) and cause us to freeze
1392  // once we attempt to climb the parents. We need to take care of that.
1393 
1394  bool bMessageWasThreadable = false;
1395  MessageItem * pParent;
1396 
1397  // First of all try to find a "perfect parent", that is the message for that
1398  // we have the ID in the "In-Reply-To" field. This is actually done by using
1399  // MD5 caches of the message ids because of speed. Collisions are very unlikely.
1400 
1401  QByteArray md5 = mi->inReplyToIdMD5();
1402  if ( !md5.isEmpty() )
1403  {
1404  // have an In-Reply-To field MD5
1405  pParent = mThreadingCacheMessageIdMD5ToMessageItem.value( md5, 0 );
1406  if(pParent)
1407  {
1408  // Take care of circular references
1409  if (
1410  ( mi == pParent ) || // self referencing message
1411  (
1412  ( mi->childItemCount() > 0 ) && // mi already has children, this is fast to determine
1413  pParent->hasAncestor( mi ) // pParent is in the mi's children tree
1414  )
1415  )
1416  {
1417  kWarning() << "Circular In-Reply-To reference loop detected in the message tree";
1418  mi->setThreadingStatus( MessageItem::NonThreadable );
1419  return 0; // broken message: throw it away
1420  }
1421  mi->setThreadingStatus( MessageItem::PerfectParentFound );
1422  return pParent; // got a perfect parent for this message
1423  }
1424 
1425  // got no perfect parent
1426  bMessageWasThreadable = true; // but the message was threadable
1427  }
1428 
1429  if ( mAggregation->threading() == Aggregation::PerfectOnly )
1430  {
1431  mi->setThreadingStatus( bMessageWasThreadable ? MessageItem::ParentMissing : MessageItem::NonThreadable );
1432  return 0; // we're doing only perfect parent matches
1433  }
1434 
1435  // Try to use the "References" field. In fact we have the MD5 of the
1436  // (n-1)th entry in References.
1437  //
1438  // Original rationale from KMHeaders:
1439  //
1440  // If we don't have a replyToId, or if we have one and the
1441  // corresponding message is not in this folder, as happens
1442  // if you keep your outgoing messages in an OUTBOX, for
1443  // example, try the list of references, because the second
1444  // to last will likely be in this folder. replyToAuxIdMD5
1445  // contains the second to last one.
1446 
1447  md5 = mi->referencesIdMD5();
1448  if ( !md5.isEmpty() )
1449  {
1450  pParent = mThreadingCacheMessageIdMD5ToMessageItem.value( md5, 0 );
1451  if(pParent)
1452  {
1453  // Take care of circular references
1454  if (
1455  ( mi == pParent ) || // self referencing message
1456  (
1457  ( mi->childItemCount() > 0 ) && // mi already has children, this is fast to determine
1458  pParent->hasAncestor( mi ) // pParent is in the mi's children tree
1459  )
1460  )
1461  {
1462  kWarning() << "Circular reference loop detected in the message tree";
1463  mi->setThreadingStatus( MessageItem::NonThreadable );
1464  return 0; // broken message: throw it away
1465  }
1466  mi->setThreadingStatus( MessageItem::ImperfectParentFound );
1467  return pParent; // got an imperfect parent for this message
1468  }
1469 
1470  // got no imperfect parent
1471  bMessageWasThreadable = true; // but the message was threadable
1472  }
1473 
1474  if ( mAggregation->threading() == Aggregation::PerfectAndReferences )
1475  {
1476  mi->setThreadingStatus( bMessageWasThreadable ? MessageItem::ParentMissing : MessageItem::NonThreadable );
1477  return 0; // we're doing only perfect parent matches
1478  }
1479 
1480  Q_ASSERT( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject );
1481 
1482  // We are supposed to do subject based threading but we can't do it now.
1483  // This is because the subject based threading *may* be wrong and waste
1484  // time by creating circular references (that we'd need to detect and fix).
1485  // We first try the perfect and references based threading on all the messages
1486  // and then run subject based threading only on the remaining ones.
1487 
1488  mi->setThreadingStatus( ( bMessageWasThreadable || mi->subjectIsPrefixed() ) ? MessageItem::ParentMissing : MessageItem::NonThreadable );
1489  return 0;
1490 }
1491 
1492 // Subject threading cache stuff
1493 
1494 #if 0
1495 // Debug helpers
1496 void dump_iterator_and_list( QList< MessageItem * >::Iterator &iter, QList< MessageItem * > *list )
1497 {
1498  kDebug() << "Threading cache part dump" << endl;
1499  if ( iter == list->end() )
1500  kDebug() << "Iterator pointing to end of the list" << endl;
1501  else
1502  kDebug() << "Iterator pointing to " << *iter << " subject [" << (*iter)->subject() << "] date [" << (*iter)->date() << "]" << endl;
1503 
1504  for ( QList< MessageItem * >::Iterator it = list->begin(); it != list->end(); ++it )
1505  {
1506  kDebug() << "List element " << *it << " subject [" << (*it)->subject() << "] date [" << (*it)->date() << "]" << endl;
1507  }
1508 
1509  kDebug() << "End of threading cache part dump" << endl;
1510 }
1511 
1512 void dump_list( QList< MessageItem * > *list )
1513 {
1514  kDebug() << "Threading cache part dump" << endl;
1515 
1516  for ( QList< MessageItem * >::Iterator it = list->begin(); it != list->end(); ++it )
1517  {
1518  kDebug() << "List element " << *it << " subject [" << (*it)->subject() << "] date [" << (*it)->date() << "]" << endl;
1519  }
1520 
1521  kDebug() << "End of threading cache part dump" << endl;
1522 }
1523 #endif // debug helpers
1524 
1525 // a helper class used in a qLowerBound() call below
1526 class MessageLessThanByDate
1527 {
1528 public:
1529  inline bool operator()( const MessageItem * mi1, const MessageItem * mi2 ) const
1530  {
1531  if ( mi1->date() < mi2->date() ) // likely
1532  return true;
1533  if ( mi1->date() > mi2->date() ) // likely
1534  return false;
1535  // dates are equal, compare by pointer
1536  return mi1 < mi2;
1537  }
1538 };
1539 
1540 void ModelPrivate::addMessageToSubjectBasedThreadingCache( MessageItem * mi )
1541 {
1542  // Messages in this cache are sorted by date, and if dates are equal then they are sorted by pointer value.
1543  // Sorting by date is used to optimize the parent lookup in guessMessageParent() below.
1544 
1545  // WARNING: If the message date changes for some reason (like in the "update" step)
1546  // then the cache may become unsorted. For this reason the message about to
1547  // be changed must be first removed from the cache and then reinserted.
1548 
1549  // Lookup the list of messages with the same stripped subject
1550  QList< MessageItem * > * messagesWithTheSameStrippedSubject =
1551  mThreadingCacheMessageSubjectMD5ToMessageItem.value( mi->strippedSubjectMD5(), 0 );
1552 
1553  if ( !messagesWithTheSameStrippedSubject )
1554  {
1555  // Not there yet: create it and append.
1556  messagesWithTheSameStrippedSubject = new QList< MessageItem * >();
1557  mThreadingCacheMessageSubjectMD5ToMessageItem.insert( mi->strippedSubjectMD5(), messagesWithTheSameStrippedSubject );
1558  messagesWithTheSameStrippedSubject->append( mi );
1559  return;
1560  }
1561 
1562  // Found: assert that we have no duplicates in the cache.
1563  Q_ASSERT( !messagesWithTheSameStrippedSubject->contains( mi ) );
1564 
1565  // Ordered insert: first by date then by pointer value.
1566  QList< MessageItem * >::Iterator it = qLowerBound( messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate() );
1567  messagesWithTheSameStrippedSubject->insert( it, mi );
1568 }
1569 
1570 void ModelPrivate::removeMessageFromSubjectBasedThreadingCache( MessageItem * mi )
1571 {
1572  // We assume that the caller knows what he is doing and the message is actually in the cache.
1573  // If the message isn't in the cache then we should be called at all.
1574  //
1575  // The game is called "performance"
1576 
1577  // Grab the list of all the messages with the same stripped subject (all potential parents)
1578  QList< MessageItem * > * messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value( mi->strippedSubjectMD5(), 0 );
1579 
1580  // We assume that the message is there so the list must be non null.
1581  Q_ASSERT( messagesWithTheSameStrippedSubject );
1582 
1583  // The cache *MUST* be ordered first by date then by pointer value
1584  QList< MessageItem * >::Iterator it = qLowerBound( messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate() );
1585 
1586  // The binary based search must have found a message
1587  Q_ASSERT( it != messagesWithTheSameStrippedSubject->end() );
1588 
1589  // and it must have found exactly the message requested
1590  Q_ASSERT( *it == mi );
1591 
1592  // Kill it
1593  messagesWithTheSameStrippedSubject->erase( it );
1594 
1595  // And kill the list if it was the last one
1596  if ( messagesWithTheSameStrippedSubject->isEmpty() )
1597  {
1598  mThreadingCacheMessageSubjectMD5ToMessageItem.remove( mi->strippedSubjectMD5() );
1599  delete messagesWithTheSameStrippedSubject;
1600  }
1601 }
1602 
1603 MessageItem * ModelPrivate::guessMessageParent( MessageItem * mi )
1604 {
1605  // This function implements subject based threading
1606  // It attempts to guess a thread parent for the item "mi"
1607  // which actually may already have a children subtree.
1608 
1609  // We have all the problems of findMessageParent() plus the fact that
1610  // we're actually guessing (and often we may be *wrong*).
1611 
1612  Q_ASSERT( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject ); // caller must take care of this
1613  Q_ASSERT( mi->subjectIsPrefixed() ); // caller must take care of this
1614  Q_ASSERT( mi->threadingStatus() == MessageItem::ParentMissing );
1615 
1616 
1617  // Do subject based threading
1618  const QByteArray md5 = mi->strippedSubjectMD5();
1619  if ( !md5.isEmpty() )
1620  {
1621  QList< MessageItem * > * messagesWithTheSameStrippedSubject =
1622  mThreadingCacheMessageSubjectMD5ToMessageItem.value( md5, 0 );
1623 
1624  if ( messagesWithTheSameStrippedSubject )
1625  {
1626  Q_ASSERT( messagesWithTheSameStrippedSubject->count() > 0 );
1627 
1628  // Need to find the message with the maximum date lower than the one of this message
1629 
1630  time_t maxTime = (time_t)0;
1631  MessageItem * pParent = 0;
1632 
1633  // Here'we re really guessing so circular references are possible
1634  // even on perfectly valid trees. This is why we don't consider it
1635  // an error but just continue searching.
1636 
1637  // FIXME: This might be speed up with an initial binary search (?)
1638  // ANSWER: No. We can't rely on date order (as it can be updated on the fly...)
1639  QList< MessageItem * >::ConstIterator end( messagesWithTheSameStrippedSubject->constEnd() );
1640 
1641  for ( QList< MessageItem * >::ConstIterator it = messagesWithTheSameStrippedSubject->constBegin(); it != end; ++it )
1642  {
1643  int delta = mi->date() - ( *it )->date();
1644 
1645  // We don't take into account messages with a delta smaller than 120.
1646  // Assuming that our date() values are correct (that is, they take into
1647  // account timezones etc..) then one usually needs more than 120 seconds
1648  // to answer to a message. Better safe than sorry.
1649 
1650  // This check also includes negative deltas so messages later than mi aren't considered
1651 
1652  if ( delta < 120 )
1653  break; // The list is ordered by date (ascending) so we can stop searching here
1654 
1655  // About the "magic" 3628899 value here comes a Till's comment from the original KMHeaders:
1656  //
1657  // "Parents more than six weeks older than the message are not accepted. The reasoning being
1658  // that if a new message with the same subject turns up after such a long time, the chances
1659  // that it is still part of the same thread are slim. The value of six weeks is chosen as a
1660  // result of a poll conducted on kde-devel, so it's probably bogus. :)"
1661 
1662  if ( delta < 3628899 )
1663  {
1664  // Compute the closest.
1665  if ( ( maxTime < ( *it )->date() ) )
1666  {
1667  // This algorithm *can* be (and often is) wrong.
1668  // Take care of circular threading which is really possible at this level.
1669  // If mi contains (*it) inside its children subtree then we have
1670  // found such a circular threading problem.
1671 
1672  // Note that here we can't have *it == mi because of the delta >= 120 check above.
1673 
1674  if ( ( mi->childItemCount() == 0 ) || !( *it )->hasAncestor( mi ) )
1675  {
1676  maxTime = ( *it )->date();
1677  pParent = ( *it );
1678  }
1679  }
1680  }
1681  }
1682 
1683  if ( pParent )
1684  {
1685  mi->setThreadingStatus( MessageItem::ImperfectParentFound );
1686  return pParent; // got an imperfect parent for this message
1687  }
1688  }
1689  }
1690 
1691  return 0;
1692 }
1693 
1694 //
1695 // A little template helper, hopefully inlineable.
1696 //
1697 // Return true if the specified message item is in the wrong position
1698 // inside the specified parent and needs re-sorting. Return false otherwise.
1699 // Both parent and messageItem must not be null.
1700 //
1701 // Checking if a message needs re-sorting instead of just re-sorting it
1702 // is very useful since re-sorting is an expensive operation.
1703 //
1704 template< class ItemComparator > static bool messageItemNeedsReSorting( SortOrder::SortDirection messageSortDirection,
1705  ItemPrivate *parent, MessageItem *messageItem )
1706 {
1707  if ( ( messageSortDirection == SortOrder::Ascending )
1708  || ( parent->mType == Item::Message ) )
1709  {
1710  return parent->childItemNeedsReSorting< ItemComparator, true >( messageItem );
1711  }
1712  return parent->childItemNeedsReSorting< ItemComparator, false >( messageItem );
1713 }
1714 
1715 bool ModelPrivate::handleItemPropertyChanges( int propertyChangeMask, Item * parent, Item * item )
1716 {
1717  // The facts:
1718  //
1719  // - If dates changed:
1720  // - If we're sorting messages by min/max date then at each level the messages might need resorting.
1721  // - If the thread leader is the most recent message of a thread then the uppermost
1722  // message of the thread might need re-grouping.
1723  // - If the groups are sorted by min/max date then the group might need re-sorting too.
1724  //
1725  // This function explicitly doesn't re-apply the filter when ActionItemStatus changes.
1726  // This is because filters must be re-applied due to a broader range of status variations:
1727  // this is done in viewItemJobStepInternalForJobPass1Update() instead (which is the only
1728  // place in that ActionItemStatus may be set).
1729 
1730  if( parent->type() == Item::InvisibleRoot )
1731  {
1732  // item is either a message or a group attacched to the root.
1733  // It might need resorting.
1734  if ( item->type() == Item::GroupHeader )
1735  {
1736  // item is a group header attacched to the root.
1737  if (
1738  (
1739  // max date changed
1740  ( propertyChangeMask & MaxDateChanged ) &&
1741  // groups sorted by max date
1742  ( mSortOrder->groupSorting() == SortOrder::SortGroupsByDateTimeOfMostRecent )
1743  ) || (
1744  // date changed
1745  ( propertyChangeMask & DateChanged ) &&
1746  // groups sorted by date
1747  ( mSortOrder->groupSorting() == SortOrder::SortGroupsByDateTime )
1748  )
1749  )
1750  {
1751  // This group might need re-sorting.
1752 
1753  // Groups are large container of messages so it's likely that
1754  // another message inserted will cause this group to be marked again.
1755  // So we wait until the end to do the grand final re-sorting: it will be done in Pass4.
1756  mGroupHeadersThatNeedUpdate.insert( static_cast< GroupHeaderItem * >( item ), static_cast< GroupHeaderItem * >( item ) );
1757  }
1758  } else {
1759  // item is a message. It might need re-sorting.
1760 
1761  // Since sorting is an expensive operation, we first check if it's *really* needed.
1762  // Re-sorting will actually not change min/max dates at all and
1763  // will not climb up the parent's ancestor tree.
1764 
1765  switch ( mSortOrder->messageSorting() )
1766  {
1767  case SortOrder::SortMessagesByDateTime:
1768  if ( propertyChangeMask & DateChanged ) // date changed
1769  {
1770  if ( messageItemNeedsReSorting< ItemDateComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1771  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1772  } // else date changed, but it doesn't match sorting order: no need to re-sort
1773  break;
1774  case SortOrder::SortMessagesByDateTimeOfMostRecent:
1775  if ( propertyChangeMask & MaxDateChanged ) // max date changed
1776  {
1777  if ( messageItemNeedsReSorting< ItemMaxDateComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1778  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1779  } // else max date changed, but it doesn't match sorting order: no need to re-sort
1780  break;
1781  case SortOrder::SortMessagesByActionItemStatus:
1782  if ( propertyChangeMask & ActionItemStatusChanged ) // todo status changed
1783  {
1784  if ( messageItemNeedsReSorting< ItemActionItemStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1785  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1786  } // else to do status changed, but it doesn't match sorting order: no need to re-sort
1787  break;
1788  case SortOrder::SortMessagesByUnreadStatus:
1789  if ( propertyChangeMask & UnreadStatusChanged ) // new / unread status changed
1790  {
1791  if ( messageItemNeedsReSorting< ItemUnreadStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1792  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1793  } // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
1794  break;
1795  case SortOrder::SortMessagesByImportantStatus:
1796  if ( propertyChangeMask & ImportantStatusChanged ) // important status changed
1797  {
1798  if ( messageItemNeedsReSorting< ItemImportantStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1799  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1800  } // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
1801  break;
1802  case SortOrder::SortMessagesByAttachmentStatus:
1803  if ( propertyChangeMask & AttachmentStatusChanged ) // attachment status changed
1804  {
1805  if ( messageItemNeedsReSorting< ItemAttachmentStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1806  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1807  } // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
1808  break;
1809  default:
1810  // this kind of message sorting isn't affected by the property changes: nothing to do.
1811  break;
1812  }
1813  }
1814 
1815  return false; // the invisible root isn't affected by any change.
1816  }
1817 
1818  if ( parent->type() == Item::GroupHeader )
1819  {
1820  // item is a message attacched to a GroupHeader.
1821  // It might need re-grouping or re-sorting (within the same group)
1822 
1823  // Check re-grouping here.
1824  if (
1825  (
1826  // max date changed
1827  ( propertyChangeMask & MaxDateChanged ) &&
1828  // thread leader is most recent message
1829  ( mAggregation->threadLeader() == Aggregation::MostRecentMessage )
1830  ) || (
1831  // date changed
1832  ( propertyChangeMask & DateChanged ) &&
1833  // thread leader the topmost message
1834  ( mAggregation->threadLeader() == Aggregation::TopmostMessage )
1835  )
1836  )
1837  {
1838  // Might really need re-grouping.
1839  // attachMessageToGroupHeader() will find the right group for this message
1840  // and if it's different than the current it will move it.
1841  attachMessageToGroupHeader( static_cast< MessageItem * >( item ) );
1842  // Re-grouping fixes the properties of the involved group headers
1843  // so at exit of attachMessageToGroupHeader() the parent can't be affected
1844  // by the change anymore.
1845  return false;
1846  }
1847 
1848  // Re-grouping wasn't needed. Re-sorting might be.
1849 
1850  } // else item is a message attacched to another message and might need re-sorting only.
1851 
1852  // Check if message needs re-sorting.
1853 
1854  switch ( mSortOrder->messageSorting() )
1855  {
1856  case SortOrder::SortMessagesByDateTime:
1857  if ( propertyChangeMask & DateChanged ) // date changed
1858  {
1859  if ( messageItemNeedsReSorting< ItemDateComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1860  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1861  } // else date changed, but it doesn't match sorting order: no need to re-sort
1862  break;
1863  case SortOrder::SortMessagesByDateTimeOfMostRecent:
1864  if ( propertyChangeMask & MaxDateChanged ) // max date changed
1865  {
1866  if ( messageItemNeedsReSorting< ItemMaxDateComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1867  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1868  } // else max date changed, but it doesn't match sorting order: no need to re-sort
1869  break;
1870  case SortOrder::SortMessagesByActionItemStatus:
1871  if ( propertyChangeMask & ActionItemStatusChanged ) // todo status changed
1872  {
1873  if ( messageItemNeedsReSorting< ItemActionItemStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1874  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1875  } // else to do status changed, but it doesn't match sorting order: no need to re-sort
1876  break;
1877  case SortOrder::SortMessagesByUnreadStatus:
1878  if ( propertyChangeMask & UnreadStatusChanged ) // new / unread status changed
1879  {
1880  if ( messageItemNeedsReSorting< ItemUnreadStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1881  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1882  } // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
1883  break;
1884  case SortOrder::SortMessagesByImportantStatus:
1885  if ( propertyChangeMask & ImportantStatusChanged ) // important status changed
1886  {
1887  if ( messageItemNeedsReSorting< ItemImportantStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1888  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1889  } // else important status changed, but it doesn't match sorting order: no need to re-sort
1890  break;
1891  case SortOrder::SortMessagesByAttachmentStatus:
1892  if ( propertyChangeMask & AttachmentStatusChanged ) // attachment status changed
1893  {
1894  if ( messageItemNeedsReSorting< ItemAttachmentStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1895  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1896  } // else important status changed, but it doesn't match sorting order: no need to re-sort
1897  break;
1898  default:
1899  // this kind of message sorting isn't affected by property changes: nothing to do.
1900  break;
1901  }
1902 
1903  return true; // parent might be affected too.
1904 }
1905 
1906 void ModelPrivate::messageDetachedUpdateParentProperties( Item *oldParent, MessageItem *mi )
1907 {
1908  Q_ASSERT( oldParent );
1909  Q_ASSERT( mi );
1910  Q_ASSERT( oldParent != mRootItem );
1911 
1912 
1913  // oldParent might have its properties changed because of the child removal.
1914  // propagate the changes up.
1915  for(;;)
1916  {
1917  // pParent is not the root item now. This is assured by how we enter this loop
1918  // and by the fact that handleItemPropertyChanges returns false when grandParent
1919  // is Item::InvisibleRoot. We could actually assert it here...
1920 
1921  // Check if its dates need an update.
1922  int propertyChangeMask;
1923 
1924  if ( ( mi->maxDate() == oldParent->maxDate() ) && oldParent->recomputeMaxDate() )
1925  propertyChangeMask = MaxDateChanged;
1926  else
1927  break; // from the for(;;) loop
1928 
1929  // One of the oldParent properties has changed for sure
1930 
1931  Item * grandParent = oldParent->parent();
1932 
1933  // If there is no grandParent then oldParent isn't attacched to the view.
1934  // Re-sorting / re-grouping isn't needed for sure.
1935  if ( !grandParent )
1936  break; // from the for(;;) loop
1937 
1938  // The following function will return true if grandParent may be affected by the change.
1939  // If the grandParent isn't affected, we stop climbing.
1940  if ( !handleItemPropertyChanges( propertyChangeMask, grandParent, oldParent ) )
1941  break; // from the for(;;) loop
1942 
1943  // Now we need to climb up one level and check again.
1944  oldParent = grandParent;
1945  } // for(;;) loop
1946 
1947  // If the last message was removed from a group header then this group will need an update
1948  // for sure. We will need to remove it (unless a message is attacched back to it)
1949  if ( oldParent->type() == Item::GroupHeader )
1950  {
1951  if ( oldParent->childItemCount() == 0 )
1952  mGroupHeadersThatNeedUpdate.insert( static_cast< GroupHeaderItem * >( oldParent ), static_cast< GroupHeaderItem * >( oldParent ) );
1953  }
1954 }
1955 
1956 void ModelPrivate::propagateItemPropertiesToParent( Item * item )
1957 {
1958  Item * pParent = item->parent();
1959  Q_ASSERT( pParent );
1960  Q_ASSERT( pParent != mRootItem );
1961 
1962  for(;;)
1963  {
1964  // pParent is not the root item now. This is assured by how we enter this loop
1965  // and by the fact that handleItemPropertyChanges returns false when grandParent
1966  // is Item::InvisibleRoot. We could actually assert it here...
1967 
1968  // Check if its dates need an update.
1969  int propertyChangeMask;
1970 
1971  if ( item->maxDate() > pParent->maxDate() )
1972  {
1973  pParent->setMaxDate( item->maxDate() );
1974  propertyChangeMask = MaxDateChanged;
1975  } else {
1976  // No parent dates have changed: no further work is needed. Stop climbing here.
1977  break; // from the for(;;) loop
1978  }
1979 
1980  // One of the pParent properties has changed.
1981 
1982  Item * grandParent = pParent->parent();
1983 
1984  // If there is no grandParent then pParent isn't attacched to the view.
1985  // Re-sorting / re-grouping isn't needed for sure.
1986  if ( !grandParent )
1987  break; // from the for(;;) loop
1988 
1989  // The following function will return true if grandParent may be affected by the change.
1990  // If the grandParent isn't affected, we stop climbing.
1991  if ( !handleItemPropertyChanges( propertyChangeMask, grandParent, pParent ) )
1992  break; // from the for(;;) loop
1993 
1994  // Now we need to climb up one level and check again.
1995  pParent = grandParent;
1996 
1997  } // for(;;)
1998 }
1999 
2000 
2001 void ModelPrivate::attachMessageToParent( Item *pParent, MessageItem *mi )
2002 {
2003  Q_ASSERT( pParent );
2004  Q_ASSERT( mi );
2005 
2006  // This function may be called to do a simple "re-sort" of the item inside the parent.
2007  // In that case mi->parent() is equal to pParent.
2008  bool oldParentWasTheSame;
2009 
2010  if ( mi->parent() )
2011  {
2012  Item * oldParent = mi->parent();
2013 
2014  // The item already had a parent and this means that we're moving it.
2015  oldParentWasTheSame = oldParent == pParent; // just re-sorting ?
2016 
2017  if ( mi->isViewable() ) // is actually
2018  {
2019  // The message is actually attached to the viewable root
2020 
2021  // Unfortunately we need to hack the model/view architecture
2022  // since it's somewhat flawed in this. At the moment of writing
2023  // there is simply no way to atomically move a subtree.
2024  // We must detach, call beginRemoveRows()/endRemoveRows(),
2025  // save the expanded state, save the selection, save the current item,
2026  // save the view position (YES! As we are removing items the view
2027  // will hopelessly jump around so we're just FORCED to break
2028  // the isolation from the view)...
2029  // ...*then* reattach, restore the expanded state, restore the selection,
2030  // restore the current item, restore the view position and pray
2031  // that nothing will fail in the (rather complicated) process....
2032 
2033  // Yet more unfortunately, while saving the expanded state might stop
2034  // at a certain (unexpanded) point in the tree, saving the selection
2035  // is hopelessly recursive down to the bare leafs.
2036 
2037  // Furthermore the expansion of items is a common case while selection
2038  // in the subtree is rare, so saving it would be a huge cost with
2039  // a low revenue.
2040 
2041  // This is why we just let the selection screw up. I hereby refuse to call
2042  // yet another expensive recursive function here :D
2043 
2044  // The current item saving can be somewhat optimized doing it once for
2045  // a single job step...
2046 
2047  if (
2048  ( ( mi )->childItemCount() > 0 ) && // has children
2049  mModelForItemFunctions && // the UI is not actually disconnected
2050  mView->isExpanded( q->index( mi, 0 ) ) // is actually expanded
2051  )
2052  saveExpandedStateOfSubtree( mi );
2053  }
2054 
2055  // If the parent is viewable (so mi was viewable too) then the beginRemoveRows()
2056  // and endRemoveRows() functions of this model will be called too.
2057  oldParent->takeChildItem( mModelForItemFunctions, mi );
2058 
2059  if ( ( !oldParentWasTheSame ) && ( oldParent != mRootItem ) )
2060  messageDetachedUpdateParentProperties( oldParent, mi );
2061 
2062  } else {
2063  // The item had no parent yet.
2064  oldParentWasTheSame = false;
2065  }
2066 
2067  // Take care of perfect / imperfect threading.
2068  // Items that are now perfectly threaded, but already have a different parent
2069  // might have been imperfectly threaded before. Remove them from the caches.
2070  // Items that are now imperfectly threaded must be added to the caches.
2071  //
2072  // If we're just re-sorting the item inside the same parent then the threading
2073  // caches don't need to be updated (since they actually depend on the parent).
2074 
2075  if ( !oldParentWasTheSame )
2076  {
2077  switch( mi->threadingStatus() )
2078  {
2079  case MessageItem::PerfectParentFound:
2080  if ( !mi->inReplyToIdMD5().isEmpty() )
2081  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove( mi->inReplyToIdMD5(), mi );
2082  break;
2083  case MessageItem::ImperfectParentFound:
2084  case MessageItem::ParentMissing: // may be: temporary or just fallback assignment
2085  if ( !mi->inReplyToIdMD5().isEmpty() )
2086  {
2087  if ( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( mi->inReplyToIdMD5(), mi ) )
2088  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert( mi->inReplyToIdMD5(), mi );
2089  }
2090  break;
2091  case MessageItem::NonThreadable: // this also happens when we do no threading at all
2092  // make gcc happy
2093  Q_ASSERT( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( mi->inReplyToIdMD5(), mi ) );
2094  break;
2095  }
2096  }
2097 
2098  // Set the new parent
2099  mi->setParent( pParent );
2100 
2101  // Propagate watched and ignored status
2102  if (
2103  ( pParent->status().toQInt32() & mCachedWatchedOrIgnoredStatusBits ) && // unlikely
2104  ( pParent->type() == Item::Message ) // likely
2105  )
2106  {
2107  // the parent is either watched or ignored: propagate to the child
2108  if ( pParent->status().isWatched() )
2109  {
2110  int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow( mi );
2111  mi->setStatus( Akonadi::MessageStatus::statusWatched() );
2112  mStorageModel->setMessageItemStatus( mi, row, Akonadi::MessageStatus::statusWatched() );
2113  } else if ( pParent->status().isIgnored() )
2114  {
2115  int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow( mi );
2116  mi->setStatus( Akonadi::MessageStatus::statusIgnored() );
2117  mStorageModel->setMessageItemStatus( mi, row, Akonadi::MessageStatus::statusIgnored() );
2118  }
2119  }
2120 
2121  // And insert into its child list
2122 
2123  // If pParent is viewable then the insert/append functions will call this model's
2124  // beginInsertRows() and endInsertRows() functions. This is EXTREMELY
2125  // expensive and ugly but it's the only way with the Qt4 imposed Model/View method.
2126  // Dude... (citation from Lost, if it wasn't clear).
2127 
2128  // I'm using a macro since it does really improve readability.
2129  // I'm NOT using a helper function since gcc will refuse to inline some of
2130  // the calls because they make this function grow too much.
2131 #define INSERT_MESSAGE_WITH_COMPARATOR( _ItemComparator ) \
2132  if ( ( mSortOrder->messageSortDirection() == SortOrder::Ascending ) \
2133  || ( pParent->type() == Item::Message ) ) \
2134  { \
2135  pParent->d_ptr->insertChildItem< _ItemComparator, true >( mModelForItemFunctions, mi ); \
2136 } \
2137  else \
2138  { \
2139  pParent->d_ptr->insertChildItem< _ItemComparator, false >( mModelForItemFunctions, mi ); \
2140 }
2141 
2142  // If pParent is viewable then the insertion call will also set the child state to viewable.
2143  // Since mi MAY have children, then this call may make them viewable.
2144  switch( mSortOrder->messageSorting() )
2145  {
2146  case SortOrder::SortMessagesByDateTime:
2147  INSERT_MESSAGE_WITH_COMPARATOR( ItemDateComparator )
2148  break;
2149  case SortOrder::SortMessagesByDateTimeOfMostRecent:
2150  INSERT_MESSAGE_WITH_COMPARATOR( ItemMaxDateComparator )
2151  break;
2152  case SortOrder::SortMessagesBySize:
2153  INSERT_MESSAGE_WITH_COMPARATOR( ItemSizeComparator )
2154  break;
2155  case SortOrder::SortMessagesBySenderOrReceiver:
2156  INSERT_MESSAGE_WITH_COMPARATOR( ItemSenderOrReceiverComparator )
2157  break;
2158  case SortOrder::SortMessagesBySender:
2159  INSERT_MESSAGE_WITH_COMPARATOR( ItemSenderComparator )
2160  break;
2161  case SortOrder::SortMessagesByReceiver:
2162  INSERT_MESSAGE_WITH_COMPARATOR( ItemReceiverComparator )
2163  break;
2164  case SortOrder::SortMessagesBySubject:
2165  INSERT_MESSAGE_WITH_COMPARATOR( ItemSubjectComparator )
2166  break;
2167  case SortOrder::SortMessagesByActionItemStatus:
2168  INSERT_MESSAGE_WITH_COMPARATOR( ItemActionItemStatusComparator )
2169  break;
2170  case SortOrder::SortMessagesByUnreadStatus:
2171  INSERT_MESSAGE_WITH_COMPARATOR( ItemUnreadStatusComparator )
2172  break;
2173  case SortOrder::SortMessagesByImportantStatus:
2174  INSERT_MESSAGE_WITH_COMPARATOR( ItemImportantStatusComparator )
2175  break;
2176  case SortOrder::SortMessagesByAttachmentStatus:
2177  INSERT_MESSAGE_WITH_COMPARATOR( ItemAttachmentStatusComparator )
2178  break;
2179  case SortOrder::NoMessageSorting:
2180  pParent->appendChildItem( mModelForItemFunctions, mi );
2181  break;
2182  default: // should never happen
2183  pParent->appendChildItem( mModelForItemFunctions, mi );
2184  break;
2185  }
2186 
2187  // Decide if we need to expand parents
2188  bool childNeedsExpanding = ( mi->initialExpandStatus() == Item::ExpandNeeded );
2189 
2190  if ( pParent->initialExpandStatus() == Item::NoExpandNeeded )
2191  {
2192  switch( mAggregation->threadExpandPolicy() )
2193  {
2194  case Aggregation::NeverExpandThreads:
2195  // just do nothing unless this child has children and is already marked for expansion
2196  if ( childNeedsExpanding )
2197  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2198  break;
2199  case Aggregation::ExpandThreadsWithNewMessages: // No more new status. fall through to unread if it exists in config
2200  case Aggregation::ExpandThreadsWithUnreadMessages:
2201  // expand only if unread (or it has children marked for expansion)
2202  if ( childNeedsExpanding || !mi->status().isRead() )
2203  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2204  break;
2205  case Aggregation::ExpandThreadsWithUnreadOrImportantMessages:
2206  // expand only if unread, important or todo (or it has children marked for expansion)
2207  // FIXME: Wouldn't it be nice to be able to test for bitmasks in MessageStatus ?
2208  if ( childNeedsExpanding || !mi->status().isRead() || mi->status().isImportant() || mi->status().isToAct() )
2209  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2210  break;
2211  case Aggregation::AlwaysExpandThreads:
2212  // expand everything
2213  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2214  break;
2215  default:
2216  // BUG
2217  break;
2218  }
2219  } // else it's already marked for expansion or expansion has been already executed
2220 
2221  // expand parent first, if possible
2222  if ( pParent->initialExpandStatus() == Item::ExpandNeeded )
2223  {
2224  // If UI is not disconnected and parent is viewable, go up and expand
2225  if ( mModelForItemFunctions && pParent->isViewable() )
2226  {
2227  // Now expand parents as needed
2228  Item * parentToExpand = pParent;
2229  while ( parentToExpand )
2230  {
2231  if ( parentToExpand == mRootItem )
2232  break; // no need to set it expanded
2233  // parentToExpand is surely viewable (because this item is)
2234  if ( parentToExpand->initialExpandStatus() == Item::ExpandExecuted )
2235  break;
2236 
2237  mView->expand( q->index( parentToExpand, 0 ) );
2238 
2239  parentToExpand->setInitialExpandStatus( Item::ExpandExecuted );
2240  parentToExpand = parentToExpand->parent();
2241  }
2242  } else {
2243  // It isn't viewable or UI is disconnected: climb up marking only
2244  Item * parentToExpand = pParent->parent();
2245  while ( parentToExpand )
2246  {
2247  if ( parentToExpand == mRootItem )
2248  break; // no need to set it expanded
2249  parentToExpand->setInitialExpandStatus( Item::ExpandNeeded );
2250  parentToExpand = parentToExpand->parent();
2251  }
2252  }
2253  }
2254 
2255  if ( mi->isViewable() )
2256  {
2257  // mi is now viewable
2258 
2259  // sync subtree expanded status
2260  if ( childNeedsExpanding )
2261  {
2262  if ( mi->childItemCount() > 0 )
2263  if ( mModelForItemFunctions ) // the UI is not disconnected
2264  syncExpandedStateOfSubtree( mi ); // sync the real state in the view
2265  }
2266 
2267  // apply the filter, if needed
2268  if ( mFilter )
2269  {
2270  Q_ASSERT( mModelForItemFunctions ); // the UI must be NOT disconnected here
2271 
2272  // apply the filter to subtree
2273  if ( applyFilterToSubtree( mi, q->index( pParent, 0 ) ) )
2274  {
2275  // mi matched, expand parents (unconditionally)
2276  mView->ensureDisplayedWithParentsExpanded( mi );
2277  }
2278  }
2279  }
2280 
2281  // Now we need to propagate the property changes the upper levels.
2282 
2283  // If we have just inserted a message inside the root then no work needs to be done:
2284  // no grouping is in effect and the message is already in the right place.
2285  if ( pParent == mRootItem )
2286  return;
2287 
2288  // If we have just removed the item from this parent and re-inserted it
2289  // then this operation was a simple re-sort. The code above didn't update
2290  // the properties when removing the item so we don't actually need
2291  // to make the updates back.
2292  if ( oldParentWasTheSame )
2293  return;
2294 
2295  // FIXME: OPTIMIZE THIS: First propagate changes THEN syncExpandedStateOfSubtree()
2296  // and applyFilterToSubtree... (needs some thinking though).
2297 
2298  // Time to propagate up.
2299  propagateItemPropertiesToParent( mi );
2300 
2301  // Aaah.. we're done. Time for a thea ? :)
2302 }
2303 
2304 // FIXME: ThreadItem ?
2305 //
2306 // Foo Bar, Joe Thommason, Martin Rox ... Eddie Maiden <date of the thread>
2307 // Title <number of messages>, Last by xxx <inner status>
2308 //
2309 // When messages are added, mark it as dirty only (?)
2310 
2311 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass5( ViewItemJob *job, const QTime &tStart )
2312 {
2313  // In this pass we scan the group headers that are in mGroupHeadersThatNeedUpdate.
2314  // Empty groups get deleted while the other ones are re-sorted.
2315  int elapsed;
2316 
2317  int curIndex = job->currentIndex();
2318 
2319  QHash< GroupHeaderItem *, GroupHeaderItem * >::Iterator it = mGroupHeadersThatNeedUpdate.begin();
2320  QHash< GroupHeaderItem *, GroupHeaderItem * >::Iterator end = mGroupHeadersThatNeedUpdate.end();
2321 
2322  while ( it != end )
2323  {
2324  if ( ( *it )->childItemCount() == 0 )
2325  {
2326  // group with no children, kill it
2327  ( *it )->parent()->takeChildItem( mModelForItemFunctions, *it );
2328  mGroupHeaderItemHash.remove( ( *it )->label() );
2329 
2330  // If we were going to restore its position after the job step, well.. we can't do it anymore.
2331  if ( mCurrentItemToRestoreAfterViewItemJobStep == ( *it ) )
2332  mCurrentItemToRestoreAfterViewItemJobStep = 0;
2333 
2334  // bye bye
2335  delete *it;
2336  } else {
2337  // Group with children: probably needs re-sorting.
2338 
2339  // Re-sorting here is an expensive operation.
2340  // In fact groups have been put in the QHash above on the assumption
2341  // that re-sorting *might* be needed but no real (expensive) check
2342  // has been done yet. Also by sorting a single group we might actually
2343  // put the others in the right place.
2344  // So finally check if re-sorting is *really* needed.
2345  bool needsReSorting;
2346 
2347  // A macro really improves readability here.
2348 #define CHECK_IF_GROUP_NEEDS_RESORTING( _ItemDateComparator ) \
2349  switch ( mSortOrder->groupSortDirection() ) \
2350  { \
2351  case SortOrder::Ascending: \
2352  needsReSorting = ( *it )->parent()->d_ptr->childItemNeedsReSorting< _ItemDateComparator, true >( *it ); \
2353  break; \
2354  case SortOrder::Descending: \
2355  needsReSorting = ( *it )->parent()->d_ptr->childItemNeedsReSorting< _ItemDateComparator, false >( *it ); \
2356  break; \
2357  default: /* should never happen */ \
2358  needsReSorting = false; \
2359  break; \
2360  }
2361 
2362  switch ( mSortOrder->groupSorting() )
2363  {
2364  case SortOrder::SortGroupsByDateTime:
2365  CHECK_IF_GROUP_NEEDS_RESORTING( ItemDateComparator )
2366  break;
2367  case SortOrder::SortGroupsByDateTimeOfMostRecent:
2368  CHECK_IF_GROUP_NEEDS_RESORTING( ItemMaxDateComparator )
2369  break;
2370  case SortOrder::SortGroupsBySenderOrReceiver:
2371  CHECK_IF_GROUP_NEEDS_RESORTING( ItemSenderOrReceiverComparator )
2372  break;
2373  case SortOrder::SortGroupsBySender:
2374  CHECK_IF_GROUP_NEEDS_RESORTING( ItemSenderComparator )
2375  break;
2376  case SortOrder::SortGroupsByReceiver:
2377  CHECK_IF_GROUP_NEEDS_RESORTING( ItemReceiverComparator )
2378  break;
2379  case SortOrder::NoGroupSorting:
2380  needsReSorting = false;
2381  break;
2382  default:
2383  // Should never happen... just assume re-sorting is not needed
2384  needsReSorting = false;
2385  break;
2386  }
2387 
2388  if ( needsReSorting )
2389  attachGroup( *it ); // it will first detach and then re-attach in the proper place
2390  }
2391 
2392  it = mGroupHeadersThatNeedUpdate.erase( it );
2393 
2394  curIndex++;
2395 
2396  // FIXME: In fact a single update is likely to manipulate
2397  // a subtree with a LOT of messages inside. If interactivity is favored
2398  // we should check the time really more often.
2399  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2400  {
2401  elapsed = tStart.msecsTo( QTime::currentTime() );
2402  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2403  {
2404  if ( it != mGroupHeadersThatNeedUpdate.end() )
2405  {
2406  job->setCurrentIndex( curIndex );
2407  return ViewItemJobInterrupted;
2408  }
2409  }
2410  }
2411 
2412  }
2413 
2414  return ViewItemJobCompleted;
2415 }
2416 
2417 
2418 
2419 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass4( ViewItemJob *job, const QTime &tStart )
2420 {
2421  // In this pass we scan mUnassignedMessageListForPass4 which now
2422  // contains both items with parents and items without parents.
2423  // We scan mUnassignedMessageList for messages without parent (the ones that haven't been
2424  // attacched to the viewable tree yet) and find a suitable group for them. Then we simply
2425  // clear mUnassignedMessageList.
2426 
2427  // We call this pass "Grouping"
2428 
2429  int elapsed;
2430 
2431  int curIndex = job->currentIndex();
2432  int endIndex = job->endIndex();
2433 
2434  while ( curIndex <= endIndex )
2435  {
2436  MessageItem * mi = mUnassignedMessageListForPass4[curIndex];
2437  if ( !mi->parent() )
2438  {
2439  // Unassigned item: thread leader, insert into the proper group.
2440  // Locate the group (or root if no grouping requested)
2441  attachMessageToGroupHeader( mi );
2442  } else {
2443  // A parent was already assigned in Pass3: we have nothing to do here
2444  }
2445  curIndex++;
2446 
2447  // FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
2448  // a subtree with a LOT of messages inside. If interactivity is favored
2449  // we should check the time really more often.
2450  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2451  {
2452  elapsed = tStart.msecsTo( QTime::currentTime() );
2453  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2454  {
2455  if ( curIndex <= endIndex )
2456  {
2457  job->setCurrentIndex( curIndex );
2458  return ViewItemJobInterrupted;
2459  }
2460  }
2461  }
2462  }
2463 
2464  mUnassignedMessageListForPass4.clear();
2465  return ViewItemJobCompleted;
2466 }
2467 
2468 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass3( ViewItemJob *job, const QTime &tStart )
2469 {
2470  // In this pass we scan the mUnassignedMessageListForPass3 and try to do construct the threads
2471  // by using subject based threading. If subject based threading is not in effect then
2472  // this pass turns to a nearly-no-op: at the end of Pass2 we have swapped the lists
2473  // and mUnassignedMessageListForPass3 is actually empty.
2474 
2475  // We don't shrink the mUnassignedMessageListForPass3 for two reasons:
2476  // - It would mess up this chunked algorithm by shifting indexes
2477  // - mUnassignedMessageList is a QList which is basically an array. It's faster
2478  // to traverse an array of N entries than to remove K>0 entries one by one and
2479  // to traverse the remaining N-K entries.
2480 
2481  int elapsed;
2482 
2483  int curIndex = job->currentIndex();
2484  int endIndex = job->endIndex();
2485 
2486  while ( curIndex <= endIndex )
2487  {
2488  // If we're here, then threading is requested for sure.
2489  MessageItem * mi = mUnassignedMessageListForPass3[curIndex];
2490  if ( ( !mi->parent() ) || ( mi->threadingStatus() == MessageItem::ParentMissing ) )
2491  {
2492  // Parent is missing (either "physically" with the item being not attacched or "logically"
2493  // with the item being attacched to a group or directly to the root.
2494  if ( mi->subjectIsPrefixed() )
2495  {
2496  // We can try to guess it
2497  MessageItem * mparent = guessMessageParent( mi );
2498 
2499  if ( mparent )
2500  {
2501  // imperfect parent found
2502  if ( mi->isViewable() )
2503  {
2504  // mi was already viewable, we're just trying to re-parent it better...
2505  attachMessageToParent( mparent, mi );
2506  if ( !mparent->isViewable() )
2507  {
2508  // re-attach it immediately (so current item is not lost)
2509  MessageItem * topmost = mparent->topmostMessage();
2510  Q_ASSERT( !topmost->parent() ); // groups are always viewable!
2511  topmost->setThreadingStatus( MessageItem::ParentMissing );
2512  attachMessageToGroupHeader( topmost );
2513  }
2514  } else {
2515  // mi wasn't viewable yet.. no need to attach parent
2516  attachMessageToParent( mparent, mi );
2517  }
2518  // and we're done for now
2519  } else {
2520  // so parent not found, (threadingStatus() is either MessageItem::ParentMissing or MessageItem::NonThreadable)
2521  Q_ASSERT( ( mi->threadingStatus() == MessageItem::ParentMissing ) || ( mi->threadingStatus() == MessageItem::NonThreadable ) );
2522  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2523  // and wait for Pass4
2524  }
2525  } else {
2526  // can't guess the parent as the subject isn't prefixed
2527  Q_ASSERT( ( mi->threadingStatus() == MessageItem::ParentMissing ) || ( mi->threadingStatus() == MessageItem::NonThreadable ) );
2528  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2529  // and wait for Pass4
2530  }
2531  } else {
2532  // Has a parent: either perfect parent already found or non threadable.
2533  // Since we don't end here if mi has status of parent missing then mi must not have imperfect parent.
2534  Q_ASSERT( mi->threadingStatus() != MessageItem::ImperfectParentFound );
2535  Q_ASSERT( mi->isViewable() );
2536  }
2537 
2538  curIndex++;
2539 
2540  // FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
2541  // a subtree with a LOT of messages inside. If interactivity is favored
2542  // we should check the time really more often.
2543  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2544  {
2545  elapsed = tStart.msecsTo( QTime::currentTime() );
2546  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2547  {
2548  if ( curIndex <= endIndex )
2549  {
2550  job->setCurrentIndex( curIndex );
2551  return ViewItemJobInterrupted;
2552  }
2553  }
2554  }
2555  }
2556 
2557  mUnassignedMessageListForPass3.clear();
2558  return ViewItemJobCompleted;
2559 }
2560 
2561 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass2( ViewItemJob *job, const QTime &tStart )
2562 {
2563  // In this pass we scan the mUnassignedMessageList and try to do construct the threads.
2564  // If some thread leader message got attacched to the viewable tree in Pass1Fill then
2565  // we'll also attach all of its children too. The thread leaders we were unable
2566  // to attach in Pass1Fill and their children (which we find here) will make it to the small Pass3
2567 
2568  // We don't shrink the mUnassignedMessageList for two reasons:
2569  // - It would mess up this chunked algorithm by shifting indexes
2570  // - mUnassignedMessageList is a QList which is basically an array. It's faster
2571  // to traverse an array of N entries than to remove K>0 entries one by one and
2572  // to traverse the remaining N-K entries.
2573 
2574  // We call this pass "Threading"
2575 
2576  int elapsed;
2577 
2578  int curIndex = job->currentIndex();
2579  int endIndex = job->endIndex();
2580 
2581  while ( curIndex <= endIndex )
2582  {
2583  // If we're here, then threading is requested for sure.
2584  MessageItem * mi = mUnassignedMessageListForPass2[curIndex];
2585  // The item may or may not have a parent.
2586  // If it has no parent or it has a temporary one (mi->parent() && mi->threadingStatus() == MessageItem::ParentMissing)
2587  // then we attempt to (re-)thread it. Otherwise we just do nothing (the job has already been done by the previous steps).
2588  if ( ( !mi->parent() ) || ( mi->threadingStatus() == MessageItem::ParentMissing ) )
2589  {
2590  MessageItem * mparent = findMessageParent( mi );
2591 
2592  if ( mparent )
2593  {
2594  // parent found, either perfect or imperfect
2595  if ( mi->isViewable() )
2596  {
2597  // mi was already viewable, we're just trying to re-parent it better...
2598  attachMessageToParent( mparent, mi );
2599  if ( !mparent->isViewable() )
2600  {
2601  // re-attach it immediately (so current item is not lost)
2602  MessageItem * topmost = mparent->topmostMessage();
2603  Q_ASSERT( !topmost->parent() ); // groups are always viewable!
2604  topmost->setThreadingStatus( MessageItem::ParentMissing );
2605  attachMessageToGroupHeader( topmost );
2606  }
2607  } else {
2608  // mi wasn't viewable yet.. no need to attach parent
2609  attachMessageToParent( mparent, mi );
2610  }
2611  // and we're done for now
2612  } else {
2613  // so parent not found, (threadingStatus() is either MessageItem::ParentMissing or MessageItem::NonThreadable)
2614  switch( mi->threadingStatus() )
2615  {
2616  case MessageItem::ParentMissing:
2617  if ( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
2618  {
2619  // parent missing but still can be found in Pass3
2620  mUnassignedMessageListForPass3.append( mi ); // this is ~O(1)
2621  } else {
2622  // We're not doing subject based threading: will never be threaded, go straight to Pass4
2623  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2624  }
2625  break;
2626  case MessageItem::NonThreadable:
2627  // will never be threaded, go straight to Pass4
2628  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2629  break;
2630  default:
2631  // a bug for sure
2632  kWarning() << "ERROR: Invalid message threading status returned by findMessageParent()!";
2633  Q_ASSERT( false );
2634  break;
2635  }
2636  }
2637  } else {
2638  // Has a parent: either perfect parent already found or non threadable.
2639  // Since we don't end here if mi has status of parent missing then mi must not have imperfect parent.
2640  Q_ASSERT( mi->threadingStatus() != MessageItem::ImperfectParentFound );
2641  if ( !mi->isViewable() )
2642  {
2643  kWarning() << "Non viewable message " << mi << " subject " << mi->subject().toUtf8().data();
2644  Q_ASSERT( mi->isViewable() );
2645  }
2646  }
2647 
2648  curIndex++;
2649 
2650  // FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
2651  // a subtree with a LOT of messages inside. If interactivity is favored
2652  // we should check the time really more often.
2653  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2654  {
2655  elapsed = tStart.msecsTo( QTime::currentTime() );
2656  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2657  {
2658  if ( curIndex <= endIndex )
2659  {
2660  job->setCurrentIndex( curIndex );
2661  return ViewItemJobInterrupted;
2662  }
2663  }
2664  }
2665  }
2666 
2667  mUnassignedMessageListForPass2.clear();
2668  return ViewItemJobCompleted;
2669 }
2670 
2671 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Fill( ViewItemJob *job, const QTime &tStart )
2672 {
2673  // In this pass we scan the a contiguous region of the underlying storage (that is
2674  // assumed to be FLAT) and create the corresponding MessageItem objects.
2675  // The deal is to show items to the user as soon as possible so in this pass we
2676  // *TRY* to attach them to the viewable tree (which is rooted on mRootItem).
2677  // Messages we're unable to attach for some reason (mainly due to threading) get appended
2678  // to mUnassignedMessageList and wait for Pass2.
2679 
2680  // We call this pass "Processing"
2681 
2682  int elapsed;
2683 
2684  // Should we use the receiver or the sender field for sorting ?
2685  bool bUseReceiver = mStorageModelContainsOutboundMessages;
2686 
2687  // The begin storage index of our work
2688  int curIndex = job->currentIndex();
2689  // The end storage index of our work.
2690  int endIndex = job->endIndex();
2691 
2692  unsigned long msgToSelect = mPreSelectionMode == PreSelectLastSelected ? mStorageModel->preSelectedMessage() : 0;
2693 
2694  MessageItem * mi = 0;
2695 
2696  while( curIndex <= endIndex )
2697  {
2698  // Create the message item with no parent: we'll set it later
2699  if ( !mi )
2700  {
2701  mi = new MessageItem();
2702  } else {
2703  // a MessageItem discarded by a previous iteration: reuse it.
2704  Q_ASSERT( mi->parent() == 0 );
2705  }
2706 
2707  if ( !mStorageModel->initializeMessageItem( mi, curIndex, bUseReceiver ) )
2708  {
2709  // ugh
2710  kWarning() << "Fill of the MessageItem at storage row index " << curIndex << " failed";
2711  curIndex++;
2712  continue;
2713  }
2714 
2715  // If we're supposed to pre-select a specific message, check if it's this one.
2716  if ( msgToSelect != 0 && msgToSelect == mi->uniqueId() ) {
2717  // Found, it's this one.
2718  // But actually it's not viewable (so not selectable). We must wait
2719  // until the end of the job to be 100% sure. So here we just translate
2720  // the unique id to a MessageItem pointer and wait.
2721  mLastSelectedMessageInFolder = mi;
2722  msgToSelect = 0; // already found, don't bother checking anymore
2723  }
2724 
2725  // Update the newest/oldest message, since we might be supposed to select those later
2726  if ( !mOldestItem || mOldestItem->date() > mi->date() ) {
2727  mOldestItem = mi;
2728  }
2729  if ( !mNewestItem || mNewestItem->date() < mi->date() ) {
2730  mNewestItem = mi;
2731  }
2732 
2733  // Ok.. it passed the initial checks: we will not be discarding it.
2734  // Make this message item an invariant index to the underlying model storage.
2735  mInvariantRowMapper->createModelInvariantIndex( curIndex, mi );
2736 
2737 
2738  // Attempt to do threading as soon as possible (to display items to the user)
2739  if ( mAggregation->threading() != Aggregation::NoThreading )
2740  {
2741  // Threading is requested
2742 
2743  // Fetch the data needed for proper threading
2744  // Add the item to the threading caches
2745 
2746  switch( mAggregation->threading() )
2747  {
2748  case Aggregation::PerfectReferencesAndSubject:
2749  mStorageModel->fillMessageItemThreadingData( mi, curIndex, StorageModel::PerfectThreadingReferencesAndSubject );
2750 
2751  // We also need to build the subject-based threading cache
2752  addMessageToSubjectBasedThreadingCache( mi );
2753  break;
2754  case Aggregation::PerfectAndReferences:
2755  mStorageModel->fillMessageItemThreadingData( mi, curIndex, StorageModel::PerfectThreadingPlusReferences );
2756  break;
2757  default:
2758  mStorageModel->fillMessageItemThreadingData( mi, curIndex, StorageModel::PerfectThreadingOnly );
2759  break;
2760  }
2761 
2762  // Perfect/References threading cache
2763  mThreadingCacheMessageIdMD5ToMessageItem.insert( mi->messageIdMD5(), mi );
2764 
2765  // Check if this item is a perfect parent for some imperfectly threaded
2766  // message (that is actually attacched to it, but not necessairly to the
2767  // viewable root). If it is, then remove the imperfect child from its
2768  // current parent rebuild the hierarchy on the fly.
2769 
2770  bool needsImmediateReAttach = false;
2771 
2772  if ( mThreadingCacheMessageInReplyToIdMD5ToMessageItem.count() > 0 ) // unlikely
2773  {
2774  QList< MessageItem * > lImperfectlyThreaded = mThreadingCacheMessageInReplyToIdMD5ToMessageItem.values( mi->messageIdMD5() );
2775  if ( !lImperfectlyThreaded.isEmpty() )
2776  {
2777  // must move all of the items in the perfect parent
2778  QList< MessageItem * >::ConstIterator end( lImperfectlyThreaded.constEnd() );
2779  for ( QList< MessageItem * >::ConstIterator it = lImperfectlyThreaded.constBegin(); it != end; ++it )
2780  {
2781  Q_ASSERT( ( *it )->parent() );
2782  Q_ASSERT( ( *it )->parent() != mi );
2783 
2784  if ( !( ( (*it)->threadingStatus() == MessageItem::ImperfectParentFound ) ||
2785  ( (*it)->threadingStatus() == MessageItem::ParentMissing ) ) ) {
2786  kError() << "Got message " << (*it) << " with threading status" << (*it)->threadingStatus();
2787  Q_ASSERT_X( false, "ModelPrivate::viewItemJobStepInternalForJobPass1Fill", "Wrong threading status" );
2788  }
2789 
2790  // If the item was already attached to the view then
2791  // re-attach it immediately. This will avoid a message
2792  // being displayed for a short while in the view and then
2793  // disappear until a perfect parent isn't found.
2794  if ( ( *it )->isViewable() )
2795  needsImmediateReAttach = true;
2796 
2797  ( *it )->setThreadingStatus( MessageItem::PerfectParentFound );
2798  attachMessageToParent( mi, *it );
2799  }
2800  }
2801  }
2802 
2803  // FIXME: Might look by "References" too, here... (?)
2804 
2805  // Attempt to do threading with anything we already have in caches until now
2806  // Note that this is likely to work since thread-parent messages tend
2807  // to come before thread-children messages in the folders (simply because of
2808  // date of arrival).
2809 
2810  Item * pParent;
2811 
2812  // First of all try to find a "perfect parent", that is the message for that
2813  // we have the ID in the "In-Reply-To" field. This is actually done by using
2814  // MD5 caches of the message ids because of speed. Collisions are very unlikely.
2815 
2816  const QByteArray md5 = mi->inReplyToIdMD5();
2817 
2818  if ( !md5.isEmpty() )
2819  {
2820  // Have an In-Reply-To field MD5.
2821  // In well behaved mailing lists 70% of the threadable messages get a parent here :)
2822  pParent = mThreadingCacheMessageIdMD5ToMessageItem.value( md5, 0 );
2823 
2824  if( pParent ) // very likely
2825  {
2826  // Take care of self-referencing (which is always possible)
2827  // and circular In-Reply-To reference loops which are possible
2828  // in case this item was found to be a perfect parent for some
2829  // imperfectly threaded message just above.
2830  if (
2831  ( mi == pParent ) || // self referencing message
2832  (
2833  ( mi->childItemCount() > 0 ) && // mi already has children, this is fast to determine
2834  pParent->hasAncestor( mi ) // pParent is in the mi's children tree
2835  )
2836  )
2837  {
2838  // Bad, bad message.. it has In-Reply-To equal to Message-Id
2839  // or it's in a circular In-Reply-To reference loop.
2840  // Will wait for Pass2 with References-Id only
2841  kWarning() << "Circular In-Reply-To reference loop detected in the message tree";
2842  mUnassignedMessageListForPass2.append( mi );
2843  } else {
2844  // wow, got a perfect parent for this message!
2845  mi->setThreadingStatus( MessageItem::PerfectParentFound );
2846  attachMessageToParent( pParent, mi );
2847  // we're done with this message (also for Pass2)
2848  }
2849  } else {
2850  // got no parent
2851  // will have to wait Pass2
2852  mUnassignedMessageListForPass2.append( mi );
2853  }
2854  } else {
2855  // No In-Reply-To header.
2856 
2857  bool mightHaveOtherMeansForThreading;
2858 
2859  switch( mAggregation->threading() )
2860  {
2861  case Aggregation::PerfectReferencesAndSubject:
2862  mightHaveOtherMeansForThreading = mi->subjectIsPrefixed() || !mi->referencesIdMD5().isEmpty();
2863  break;
2864  case Aggregation::PerfectAndReferences:
2865  mightHaveOtherMeansForThreading = !mi->referencesIdMD5().isEmpty();
2866  break;
2867  case Aggregation::PerfectOnly:
2868  mightHaveOtherMeansForThreading = false;
2869  break;
2870  default:
2871  // BUG: there shouldn't be other values (NoThreading is excluded in an upper branch)
2872  Q_ASSERT( false );
2873  mightHaveOtherMeansForThreading = false; // make gcc happy
2874  break;
2875  }
2876 
2877  if ( mightHaveOtherMeansForThreading )
2878  {
2879  // We might have other means for threading this message, wait until Pass2
2880  mUnassignedMessageListForPass2.append( mi );
2881  } else {
2882  // No other means for threading this message. This is either
2883  // a standalone message or a thread leader.
2884  // If there is no grouping in effect or thread leaders are just the "topmost"
2885  // messages then we might be done with this one.
2886  if (
2887  ( mAggregation->grouping() == Aggregation::NoGrouping ) ||
2888  ( mAggregation->threadLeader() == Aggregation::TopmostMessage )
2889  )
2890  {
2891  // We're done with this message: it will be surely either toplevel (no grouping in effect)
2892  // or a thread leader with a well defined group. Do it :)
2893  //kDebug() << "Setting message status from " << mi->threadingStatus() << " to non threadable (1) " << mi;
2894  mi->setThreadingStatus( MessageItem::NonThreadable );
2895  // Locate the parent group for this item
2896  attachMessageToGroupHeader( mi );
2897  // we're done with this message (also for Pass2)
2898  } else {
2899  // Threads belong to the most recent message in the thread. This means
2900  // that we have to wait until Pass2 or Pass3 to assign a group.
2901  mUnassignedMessageListForPass2.append( mi );
2902  }
2903  }
2904  }
2905 
2906  if ( needsImmediateReAttach && !mi->isViewable() )
2907  {
2908  // The item gathered previously viewable children. They must be immediately
2909  // re-shown. So this item must currently be attached to the view.
2910  // This is a temporary measure: it will be probably still moved.
2911  MessageItem * topmost = mi->topmostMessage();
2912  Q_ASSERT( topmost->threadingStatus() == MessageItem::ParentMissing );
2913  attachMessageToGroupHeader( topmost );
2914  }
2915 
2916  } else {
2917  // else no threading requested: we don't even need Pass2
2918  // set not threadable status (even if it might be not true, but in this mode we don't care)
2919  //kDebug() << "Setting message status from " << mi->threadingStatus() << " to non threadable (2) " << mi;
2920  mi->setThreadingStatus( MessageItem::NonThreadable );
2921  // locate the parent group for this item
2922  if ( mAggregation->grouping() == Aggregation::NoGrouping )
2923  attachMessageToParent( mRootItem, mi ); // no groups requested, attach directly to root
2924  else
2925  attachMessageToGroupHeader( mi );
2926  // we're done with this message (also for Pass2)
2927  }
2928 
2929  mi = 0; // this item was pushed somewhere, create a new one at next iteration
2930  curIndex++;
2931 
2932  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2933  {
2934  elapsed = tStart.msecsTo( QTime::currentTime() );
2935  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2936  {
2937  if ( curIndex <= endIndex )
2938  {
2939  job->setCurrentIndex( curIndex );
2940  return ViewItemJobInterrupted;
2941  }
2942  }
2943  }
2944  }
2945 
2946  if ( mi )
2947  delete mi;
2948  return ViewItemJobCompleted;
2949 }
2950 
2951 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Cleanup( ViewItemJob *job, const QTime &tStart )
2952 {
2953  Q_ASSERT( mModelForItemFunctions ); // UI must be not disconnected here
2954  // In this pass we remove the MessageItem objects that are present in the job
2955  // and put their children in the unassigned message list.
2956 
2957  // Note that this list in fact contains MessageItem objects (we need dynamic_cast<>).
2958  QList< ModelInvariantIndex * > * invalidatedMessages = job->invariantIndexList();
2959 
2960  // We don't shrink the invalidatedMessages because it's basically an array.
2961  // It's faster to traverse an array of N entries than to remove K>0 entries
2962  // one by one and to traverse the remaining N-K entries.
2963 
2964  int elapsed;
2965 
2966  // The begin index of our work
2967  int curIndex = job->currentIndex();
2968  // The end index of our work.
2969  int endIndex = job->endIndex();
2970 
2971  if ( curIndex == job->startIndex() )
2972  Q_ASSERT( mOrphanChildrenHash.isEmpty() );
2973 
2974  while( curIndex <= endIndex )
2975  {
2976  // Get the underlying storage message data...
2977  MessageItem * dyingMessage = dynamic_cast< MessageItem * >( invalidatedMessages->at( curIndex ) );
2978  // This MUST NOT be null (otherwise we have a bug somewhere in this file).
2979  Q_ASSERT( dyingMessage );
2980 
2981  // If we were going to pre-select this message but we were interrupted
2982  // *before* it was actually made viewable, we just clear the pre-selection pointer
2983  // and unique id (abort pre-selection).
2984  if ( dyingMessage == mLastSelectedMessageInFolder )
2985  {
2986  mLastSelectedMessageInFolder = 0;
2987  mPreSelectionMode = PreSelectNone;
2988  }
2989 
2990  // remove the message from any pending user job
2991  if ( mPersistentSetManager )
2992  {
2993  mPersistentSetManager->removeMessageItemFromAllSets( dyingMessage );
2994  if ( mPersistentSetManager->setCount() < 1 )
2995  {
2996  delete mPersistentSetManager;
2997  mPersistentSetManager = 0;
2998  }
2999  }
3000 
3001  if ( dyingMessage->parent() )
3002  {
3003  // Handle saving the current selection: if this item was the current before the step
3004  // then zero it out. We have killed it and it's OK for the current item to change.
3005 
3006  if ( dyingMessage == mCurrentItemToRestoreAfterViewItemJobStep )
3007  {
3008  Q_ASSERT( dyingMessage->isViewable() );
3009  // Try to select the item below the removed one as it helps in doing a "readon" of emails:
3010  // you read a message, decide to delete it and then go to the next.
3011  // Qt tends to select the message above the removed one instead (this is a hardcoded logic in
3012  // QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved()).
3013  mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemAfter( dyingMessage, MessageTypeAny, false );
3014 
3015  if ( !mCurrentItemToRestoreAfterViewItemJobStep )
3016  {
3017  // There is no item below. Try the item above.
3018  // We still do it better than qt which tends to find the *thread* above
3019  // instead of the item above.
3020  mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemBefore( dyingMessage, MessageTypeAny, false );
3021  }
3022 
3023  Q_ASSERT( (!mCurrentItemToRestoreAfterViewItemJobStep) || mCurrentItemToRestoreAfterViewItemJobStep->isViewable() );
3024  }
3025 
3026  if (
3027  dyingMessage->isViewable() &&
3028  ( ( dyingMessage )->childItemCount() > 0 ) && // has children
3029  mView->isExpanded( q->index( dyingMessage, 0 ) ) // is actually expanded
3030  )
3031  saveExpandedStateOfSubtree( dyingMessage );
3032 
3033  Item * oldParent = dyingMessage->parent();
3034  oldParent->takeChildItem( q, dyingMessage );
3035 
3036  // FIXME: This can generate many message movements.. it would be nicer
3037  // to start from messages that are higher in the hierarchy so
3038  // we would need to move less stuff above.
3039 
3040  if ( oldParent != mRootItem )
3041  messageDetachedUpdateParentProperties( oldParent, dyingMessage );
3042 
3043  // We might have already removed its parent from the view, so it
3044  // might already be in the orphan child hash...
3045  if ( dyingMessage->threadingStatus() == MessageItem::ParentMissing )
3046  mOrphanChildrenHash.remove( dyingMessage ); // this can turn to a no-op (dyingMessage not present in fact)
3047 
3048  } else {
3049  // The dying message had no parent: this should happen only if it's already an orphan
3050 
3051  Q_ASSERT( dyingMessage->threadingStatus() == MessageItem::ParentMissing );
3052  Q_ASSERT( mOrphanChildrenHash.contains( dyingMessage ) );
3053  Q_ASSERT( dyingMessage != mCurrentItemToRestoreAfterViewItemJobStep );
3054 
3055  mOrphanChildrenHash.remove( dyingMessage );
3056  }
3057 
3058  if ( mAggregation->threading() != Aggregation::NoThreading )
3059  {
3060  // Threading is requested: remove the message from threading caches.
3061 
3062  // Remove from the cache of potential parent items
3063  mThreadingCacheMessageIdMD5ToMessageItem.remove( dyingMessage->messageIdMD5() );
3064 
3065  // If we also have a cache for subject-based threading then remove the message from there too
3066  if( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
3067  removeMessageFromSubjectBasedThreadingCache( dyingMessage );
3068 
3069  // If this message wasn't perfectly parented then it might still be in another cache.
3070  switch( dyingMessage->threadingStatus() )
3071  {
3072  case MessageItem::ImperfectParentFound:
3073  case MessageItem::ParentMissing:
3074  if ( !dyingMessage->inReplyToIdMD5().isEmpty() )
3075  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove( dyingMessage->inReplyToIdMD5() );
3076  break;
3077  default:
3078  Q_ASSERT( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( dyingMessage->inReplyToIdMD5(), dyingMessage ) );
3079  // make gcc happy
3080  break;
3081  }
3082  }
3083 
3084  while ( Item * childItem = dyingMessage->firstChildItem() )
3085  {
3086  MessageItem * childMessage = dynamic_cast< MessageItem * >( childItem );
3087  Q_ASSERT( childMessage );
3088 
3089  dyingMessage->takeChildItem( q, childMessage );
3090 
3091  if ( mAggregation->threading() != Aggregation::NoThreading )
3092  {
3093  if ( childMessage->threadingStatus() == MessageItem::PerfectParentFound )
3094  {
3095  // If the child message was perfectly parented then now it had
3096  // lost its perfect parent. Add to the cache of imperfectly parented.
3097  if ( !childMessage->inReplyToIdMD5().isEmpty() )
3098  {
3099  Q_ASSERT( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( childMessage->inReplyToIdMD5(), childMessage ) );
3100  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert( childMessage->inReplyToIdMD5(), childMessage );
3101  }
3102  }
3103  }
3104 
3105  // Parent is gone
3106  childMessage->setThreadingStatus( MessageItem::ParentMissing );
3107 
3108  // If the child (or any message in its subtree) is going to be selected,
3109  // then we must immediately reattach it to a temporary group in order for the
3110  // selection to be preserved across multiple steps. Otherwise we could end
3111  // with the child-to-be-selected being non viewable at the end
3112  // of the view job step. Attach to a temporary group.
3113  if (
3114  // child is going to be re-selected
3115  ( childMessage == mCurrentItemToRestoreAfterViewItemJobStep ) ||
3116  (
3117  // there is a message that is going to be re-selected
3118  mCurrentItemToRestoreAfterViewItemJobStep &&
3119  // that message is in the childMessage subtree
3120  mCurrentItemToRestoreAfterViewItemJobStep->hasAncestor( childMessage )
3121  )
3122  )
3123  {
3124  attachMessageToGroupHeader( childMessage );
3125 
3126  Q_ASSERT( childMessage->isViewable() );
3127  }
3128 
3129  mOrphanChildrenHash.insert( childMessage, childMessage );
3130  }
3131 
3132  if ( mNewestItem == dyingMessage ) {
3133  mNewestItem = 0;
3134  }
3135  if ( mOldestItem == dyingMessage ) {
3136  mOldestItem = 0;
3137  }
3138 
3139  delete dyingMessage;
3140 
3141  curIndex++;
3142 
3143  // FIXME: Maybe we should check smaller steps here since the
3144  // code above can generate large message tree movements
3145  // for each single item we sweep in the invalidatedMessages list.
3146  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
3147  {
3148  elapsed = tStart.msecsTo( QTime::currentTime() );
3149  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3150  {
3151  if ( curIndex <= endIndex )
3152  {
3153  job->setCurrentIndex( curIndex );
3154  return ViewItemJobInterrupted;
3155  }
3156  }
3157  }
3158  }
3159 
3160  // We looped over the entire deleted message list.
3161 
3162  job->setCurrentIndex( endIndex + 1 );
3163 
3164  // A quick last cleaning pass: this is usually very fast so we don't have a real
3165  // Pass enumeration for it. We just include it as trailer of Pass1Cleanup to be executed
3166  // when job->currentIndex() > job->endIndex();
3167 
3168  // We move all the messages from the orphan child hash to the unassigned message
3169  // list and get them ready for the standard Pass2.
3170 
3171  QHash< MessageItem *, MessageItem * >::Iterator it = mOrphanChildrenHash.begin();
3172  QHash< MessageItem *, MessageItem * >::Iterator end = mOrphanChildrenHash.end();
3173 
3174  curIndex = 0;
3175 
3176  while ( it != end )
3177  {
3178  mUnassignedMessageListForPass2.append( *it );
3179 
3180  it = mOrphanChildrenHash.erase( it );
3181 
3182  // This is still interruptible
3183 
3184  curIndex++;
3185 
3186  // FIXME: We could take "larger" steps here
3187  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
3188  {
3189  elapsed = tStart.msecsTo( QTime::currentTime() );
3190  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3191  {
3192  if ( it != mOrphanChildrenHash.end() )
3193  return ViewItemJobInterrupted;
3194  }
3195  }
3196  }
3197 
3198  return ViewItemJobCompleted;
3199 }
3200 
3201 
3202 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Update( ViewItemJob *job, const QTime &tStart )
3203 {
3204  Q_ASSERT( mModelForItemFunctions ); // UI must be not disconnected here
3205 
3206  // In this pass we simply update the MessageItem objects that are present in the job.
3207 
3208  // Note that this list in fact contains MessageItem objects (we need dynamic_cast<>).
3209  QList< ModelInvariantIndex * > * messagesThatNeedUpdate = job->invariantIndexList();
3210 
3211  // We don't shrink the messagesThatNeedUpdate because it's basically an array.
3212  // It's faster to traverse an array of N entries than to remove K>0 entries
3213  // one by one and to traverse the remaining N-K entries.
3214 
3215  int elapsed;
3216 
3217  // The begin index of our work
3218  int curIndex = job->currentIndex();
3219  // The end index of our work.
3220  int endIndex = job->endIndex();
3221 
3222  while( curIndex <= endIndex )
3223  {
3224  // Get the underlying storage message data...
3225  MessageItem * message = dynamic_cast< MessageItem * >( messagesThatNeedUpdate->at( curIndex ) );
3226  // This MUST NOT be null (otherwise we have a bug somewhere in this file).
3227  Q_ASSERT( message );
3228 
3229  int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow( message );
3230 
3231  if ( row < 0 )
3232  {
3233  // Must have been invalidated (so it's basically about to be deleted)
3234  Q_ASSERT( !message->isValid() );
3235  // Skip it here.
3236  curIndex++;
3237  continue;
3238  }
3239 
3240  time_t prevDate = message->date();
3241  time_t prevMaxDate = message->maxDate();
3242  bool toDoStatus = message->status().isToAct();
3243  bool prevUnreadStatus = !message->status().isRead();
3244  bool prevImportantStatus = message->status().isImportant();
3245 
3246  // The subject based threading cache is sorted by date: we must remove
3247  // the item and re-insert it since updateMessageItemData() may change the date too.
3248  if( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
3249  removeMessageFromSubjectBasedThreadingCache( message );
3250 
3251  // Do update
3252  mStorageModel->updateMessageItemData( message, row );
3253  QModelIndex idx = q->index( message, 0 );
3254  emit q->dataChanged( idx, idx );
3255 
3256  // Reinsert the item to the cache, if needed
3257  if( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
3258  addMessageToSubjectBasedThreadingCache( message );
3259 
3260 
3261  int propertyChangeMask = 0;
3262 
3263  if ( prevDate != message->date() )
3264  propertyChangeMask |= DateChanged;
3265  if ( prevMaxDate != message->maxDate() )
3266  propertyChangeMask |= MaxDateChanged;
3267  if ( toDoStatus != message->status().isToAct() )
3268  propertyChangeMask |= ActionItemStatusChanged;
3269  if ( prevUnreadStatus != ( !message->status().isRead() ) )
3270  propertyChangeMask |= UnreadStatusChanged;
3271  if ( prevImportantStatus != ( !message->status().isImportant() ) )
3272  propertyChangeMask |= ImportantStatusChanged;
3273 
3274  if ( propertyChangeMask )
3275  {
3276  // Some message data has changed
3277  // now we need to handle the changes that might cause re-grouping/re-sorting
3278  // and propagate them to the parents.
3279 
3280  Item * pParent = message->parent();
3281 
3282  if ( pParent && ( pParent != mRootItem ) )
3283  {
3284  // The following function will return true if itemParent may be affected by the change.
3285  // If the itemParent isn't affected, we stop climbing.
3286  if ( handleItemPropertyChanges( propertyChangeMask, pParent, message ) )
3287  {
3288  Q_ASSERT( message->parent() ); // handleItemPropertyChanges() must never leave an item detached
3289 
3290  // Note that actually message->parent() may be different than pParent since
3291  // handleItemPropertyChanges() may have re-grouped it.
3292 
3293  // Time to propagate up.
3294  propagateItemPropertiesToParent( message );
3295  }
3296  } // else there is no parent so the item isn't attached to the view: re-grouping/re-sorting not needed.
3297  } // else message data didn't change an there is nothing interesting to do
3298 
3299  // (re-)apply the filter, if needed
3300  if ( mFilter && message->isViewable() )
3301  {
3302  // In all the other cases we (re-)apply the filter to the topmost subtree that this message is in.
3303  Item * pTopMostNonRoot = message->topmostNonRoot();
3304 
3305  Q_ASSERT( pTopMostNonRoot );
3306  Q_ASSERT( pTopMostNonRoot != mRootItem );
3307  Q_ASSERT( pTopMostNonRoot->parent() == mRootItem );
3308 
3309  // FIXME: The call below works, but it's expensive when we are updating
3310  // a lot of items with filtering enabled. This is because the updated
3311  // items are likely to be in the same subtree which we then filter multiple times.
3312  // A point for us is that when filtering there shouldn't be really many
3313  // items in the view so the user isn't going to update a lot of them at once...
3314  // Well... anyway, the alternative would be to write yet another
3315  // specialized routine that would update only the "message" item
3316  // above and climb up eventually hiding parents (without descending the sibling subtrees again).
3317  // If people complain about performance in this particular case I'll consider that solution.
3318 
3319  applyFilterToSubtree( pTopMostNonRoot, QModelIndex() );
3320 
3321  } // otherwise there is no filter or the item isn't viewable: very likely
3322  // left detached while propagating property changes. Will filter it
3323  // on reattach.
3324 
3325  // Done updating this message
3326 
3327  curIndex++;
3328 
3329  // FIXME: Maybe we should check smaller steps here since the
3330  // code above can generate large message tree movements
3331  // for each single item we sweep in the messagesThatNeedUpdate list.
3332  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
3333  {
3334  elapsed = tStart.msecsTo( QTime::currentTime() );
3335  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3336  {
3337  if ( curIndex <= endIndex )
3338  {
3339  job->setCurrentIndex( curIndex );
3340  return ViewItemJobInterrupted;
3341  }
3342  }
3343  }
3344  }
3345 
3346  return ViewItemJobCompleted;
3347 }
3348 
3349 
3350 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJob( ViewItemJob *job, const QTime &tStart )
3351 {
3352  // This function does a timed chunk of work for a single Fill View job.
3353  // It attempts to process messages until a timeout forces it to return to the caller.
3354 
3355  // A macro would improve readability here but since this is a good point
3356  // to place debugger breakpoints then we need it explicited.
3357  // A (template) helper would need to pass many parameters and would not be inlined...
3358 
3359  int elapsed;
3360 
3361  if ( job->currentPass() == ViewItemJob::Pass1Fill )
3362  {
3363  // We're in Pass1Fill of the job.
3364  switch ( viewItemJobStepInternalForJobPass1Fill( job, tStart ) )
3365  {
3366  case ViewItemJobInterrupted:
3367  // current job interrupted by timeout: propagate status to caller
3368  return ViewItemJobInterrupted;
3369  break;
3370  case ViewItemJobCompleted:
3371  // pass 1 has been completed
3372  // # TODO: Refactor this, make it virtual or whatever, but switch == bad, code duplication etc
3373  job->setCurrentPass( ViewItemJob::Pass2 );
3374  job->setStartIndex( 0 );
3375  job->setEndIndex( mUnassignedMessageListForPass2.count() - 1 );
3376  // take care of small jobs which never timeout by themselves because
3377  // of a small number of messages. At the end of each job check
3378  // the time used and if we're timeoutting and there is another job
3379  // then interrupt.
3380  elapsed = tStart.msecsTo( QTime::currentTime() );
3381  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3382  {
3383  return ViewItemJobInterrupted;
3384  } // else proceed with the next pass
3385  break;
3386  default:
3387  // This is *really* a BUG
3388  kWarning() << "ERROR: returned an invalid result";
3389  Q_ASSERT( false );
3390  break;
3391  }
3392  } else if ( job->currentPass() == ViewItemJob::Pass1Cleanup )
3393  {
3394  // We're in Pass1Cleanup of the job.
3395  switch ( viewItemJobStepInternalForJobPass1Cleanup( job, tStart ) )
3396  {
3397  case ViewItemJobInterrupted:
3398  // current job interrupted by timeout: propagate status to caller
3399  return ViewItemJobInterrupted;
3400  break;
3401  case ViewItemJobCompleted:
3402  // pass 1 has been completed
3403  job->setCurrentPass( ViewItemJob::Pass2 );
3404  job->setStartIndex( 0 );
3405  job->setEndIndex( mUnassignedMessageListForPass2.count() - 1 );
3406  // take care of small jobs which never timeout by themselves because
3407  // of a small number of messages. At the end of each job check
3408  // the time used and if we're timeoutting and there is another job
3409  // then interrupt.
3410  elapsed = tStart.msecsTo( QTime::currentTime() );
3411  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3412  {
3413  return ViewItemJobInterrupted;
3414  } // else proceed with the next pass
3415  break;
3416  default:
3417  // This is *really* a BUG
3418  kWarning() << "ERROR: returned an invalid result";
3419  Q_ASSERT( false );
3420  break;
3421  }
3422  } else if ( job->currentPass() == ViewItemJob::Pass1Update )
3423  {
3424  // We're in Pass1Update of the job.
3425  switch ( viewItemJobStepInternalForJobPass1Update( job, tStart ) )
3426  {
3427  case ViewItemJobInterrupted:
3428  // current job interrupted by timeout: propagate status to caller
3429  return ViewItemJobInterrupted;
3430  break;
3431  case ViewItemJobCompleted:
3432  // pass 1 has been completed
3433  // Since Pass2, Pass3 and Pass4 are empty for an Update operation
3434  // we simply skip them. (TODO: Triple-verify this assertion...).
3435  job->setCurrentPass( ViewItemJob::Pass5 );
3436  job->setStartIndex( 0 );
3437  job->setEndIndex( mGroupHeadersThatNeedUpdate.count() - 1 );
3438  // take care of small jobs which never timeout by themselves because
3439  // of a small number of messages. At the end of each job check
3440  // the time used and if we're timeoutting and there is another job
3441  // then interrupt.
3442  elapsed = tStart.msecsTo( QTime::currentTime() );
3443  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3444  {
3445  return ViewItemJobInterrupted;
3446  } // else proceed with the next pass
3447  break;
3448  default:
3449  // This is *really* a BUG
3450  kWarning() << "ERROR: returned an invalid result";
3451  Q_ASSERT( false );
3452  break;
3453  }
3454  }
3455 
3456  // Pass1Fill/Pass1Cleanup/Pass1Update has been already completed.
3457 
3458  if ( job->currentPass() == ViewItemJob::Pass2 )
3459  {
3460  // We're in Pass2 of the job.
3461  switch ( viewItemJobStepInternalForJobPass2( job, tStart ) )
3462  {
3463  case ViewItemJobInterrupted:
3464  // current job interrupted by timeout: propagate status to caller
3465  return ViewItemJobInterrupted;
3466  break;
3467  case ViewItemJobCompleted:
3468  // pass 2 has been completed
3469  job->setCurrentPass( ViewItemJob::Pass3 );
3470  job->setStartIndex( 0 );
3471  job->setEndIndex( mUnassignedMessageListForPass3.count() - 1 );
3472  // take care of small jobs which never timeout by themselves because
3473  // of a small number of messages. At the end of each job check
3474  // the time used and if we're timeoutting and there is another job
3475  // then interrupt.
3476  elapsed = tStart.msecsTo( QTime::currentTime() );
3477  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3478  return ViewItemJobInterrupted;
3479  // else proceed with the next pass
3480  break;
3481  default:
3482  // This is *really* a BUG
3483  kWarning() << "ERROR: returned an invalid result";
3484  Q_ASSERT( false );
3485  break;
3486  }
3487  }
3488 
3489  if ( job->currentPass() == ViewItemJob::Pass3 )
3490  {
3491  // We're in Pass3 of the job.
3492  switch ( viewItemJobStepInternalForJobPass3( job, tStart ) )
3493  {
3494  case ViewItemJobInterrupted:
3495  // current job interrupted by timeout: propagate status to caller
3496  return ViewItemJobInterrupted;
3497  break;
3498  case ViewItemJobCompleted:
3499  // pass 3 has been completed
3500  job->setCurrentPass( ViewItemJob::Pass4 );
3501  job->setStartIndex( 0 );
3502  job->setEndIndex( mUnassignedMessageListForPass4.count() - 1 );
3503  // take care of small jobs which never timeout by themselves because
3504  // of a small number of messages. At the end of each job check
3505  // the time used and if we're timeoutting and there is another job
3506  // then interrupt.
3507  elapsed = tStart.msecsTo( QTime::currentTime() );
3508  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3509  return ViewItemJobInterrupted;
3510  // else proceed with the next pass
3511  break;
3512  default:
3513  // This is *really* a BUG
3514  kWarning() << "ERROR: returned an invalid result";
3515  Q_ASSERT( false );
3516  break;
3517  }
3518  }
3519 
3520  if ( job->currentPass() == ViewItemJob::Pass4 )
3521  {
3522  // We're in Pass4 of the job.
3523  switch ( viewItemJobStepInternalForJobPass4( job, tStart ) )
3524  {
3525  case ViewItemJobInterrupted:
3526  // current job interrupted by timeout: propagate status to caller
3527  return ViewItemJobInterrupted;
3528  break;
3529  case ViewItemJobCompleted:
3530  // pass 4 has been completed
3531  job->setCurrentPass( ViewItemJob::Pass5 );
3532  job->setStartIndex( 0 );
3533  job->setEndIndex( mGroupHeadersThatNeedUpdate.count() - 1 );
3534  // take care of small jobs which never timeout by themselves because
3535  // of a small number of messages. At the end of each job check
3536  // the time used and if we're timeoutting and there is another job
3537  // then interrupt.
3538  elapsed = tStart.msecsTo( QTime::currentTime() );
3539  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3540  return ViewItemJobInterrupted;
3541  // else proceed with the next pass
3542  break;
3543  default:
3544  // This is *really* a BUG
3545  kWarning() << "ERROR: returned an invalid result";;
3546  Q_ASSERT( false );
3547  break;
3548  }
3549  }
3550 
3551  // Pass4 has been already completed. Proceed to Pass5.
3552  return viewItemJobStepInternalForJobPass5( job, tStart );
3553 }
3554 
3555 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3556 
3557 // Namespace to collect all the vars and functions for KDEPIM_FOLDEROPEN_PROFILE
3558 namespace Stats {
3559 
3560 // Number of existing jobs/passes
3561 static const int numberOfPasses = ViewItemJob::LastIndex;
3562 
3563 // The pass in the last call of viewItemJobStepInternal(), used to detect when
3564 // a new pass starts
3565 static int lastPass = -1;
3566 
3567 // Total number of messages in the folder
3568 static int totalMessages;
3569 
3570 // Per-Job data
3571 static int numElements[numberOfPasses];
3572 static int totalTime[numberOfPasses];
3573 static int chunks[numberOfPasses];
3574 
3575 // Time, in msecs for some special operations
3576 static int expandingTreeTime;
3577 static int layoutChangeTime;
3578 
3579 // Descriptions of the job, for nicer debug output
3580 static const char *jobDescription[numberOfPasses] = {
3581  "Creating items from messages and simple threading",
3582  "Removing messages",
3583  "Updating messages",
3584  "Additional Threading",
3585  "Subject-Based threading",
3586  "Grouping",
3587  "Group resorting + cleanup"
3588 };
3589 
3590 // Timer to track time between start of first job and end of last job
3591 static QTime firstStartTime;
3592 
3593 // Timer to track time the current job takes
3594 static QTime currentJobStartTime;
3595 
3596 // Zeros the stats, to be called when the first job starts
3597 static void resetStats()
3598 {
3599  totalMessages = 0;
3600  layoutChangeTime = 0;
3601  expandingTreeTime = 0;
3602  lastPass = -1;
3603  for ( int i = 0; i < numberOfPasses; ++i ) {
3604  numElements[i] = 0;
3605  totalTime[i] = 0;
3606  chunks[i] = 0;
3607  }
3608 }
3609 
3610 } // namespace Stats
3611 
3612 void ModelPrivate::printStatistics()
3613 {
3614  using namespace Stats;
3615  int totalTotalTime = 0;
3616  int completeTime = firstStartTime.elapsed();
3617  for ( int i = 0; i < numberOfPasses; ++i )
3618  totalTotalTime += totalTime[i];
3619 
3620  float msgPerSecond = totalMessages / ( totalTotalTime / 1000.0f );
3621  float msgPerSecondComplete = totalMessages / ( completeTime / 1000.0f );
3622 
3623  int messagesWithSameSubjectAvg = 0;
3624  int messagesWithSameSubjectMax = 0;
3625  foreach( const QList< MessageItem * > *messages, mThreadingCacheMessageSubjectMD5ToMessageItem ) {
3626  if ( messages->size() > messagesWithSameSubjectMax )
3627  messagesWithSameSubjectMax = messages->size();
3628  messagesWithSameSubjectAvg += messages->size();
3629  }
3630  messagesWithSameSubjectAvg = messagesWithSameSubjectAvg / (float)mThreadingCacheMessageSubjectMD5ToMessageItem.size();
3631 
3632  int totalThreads = 0;
3633  if ( !mGroupHeaderItemHash.isEmpty() ) {
3634  foreach( const GroupHeaderItem *groupHeader, mGroupHeaderItemHash ) {
3635  totalThreads += groupHeader->childItemCount();
3636  }
3637  }
3638  else
3639  totalThreads = mRootItem->childItemCount();
3640 
3641  kDebug() << "Finished filling the view with" << totalMessages << "messages";
3642  kDebug() << "That took" << totalTotalTime << "msecs inside the model and"
3643  << completeTime << "in total.";
3644  kDebug() << ( totalTotalTime / (float) completeTime ) * 100.0f
3645  << "percent of the time was spent in the model.";
3646  kDebug() << "Time for layoutChanged(), in msecs:" << layoutChangeTime
3647  << "(" << (layoutChangeTime / (float)totalTotalTime) * 100.0f << "percent )";
3648  kDebug() << "Time to expand tree, in msecs:" << expandingTreeTime
3649  << "(" << (expandingTreeTime / (float)totalTotalTime) * 100.0f << "percent )";
3650  kDebug() << "Number of messages per second in the model:" << msgPerSecond;
3651  kDebug() << "Number of messages per second in total:" << msgPerSecondComplete;
3652  kDebug() << "Number of threads:" << totalThreads;
3653  kDebug() << "Number of groups:" << mGroupHeaderItemHash.size();
3654  kDebug() << "Messages per thread:" << totalMessages / (float)totalThreads;
3655  kDebug() << "Threads per group:" << totalThreads / (float)mGroupHeaderItemHash.size();
3656  kDebug() << "Messages with the same subject:"
3657  << "Max:" << messagesWithSameSubjectMax
3658  << "Avg:" << messagesWithSameSubjectAvg;
3659  kDebug();
3660  kDebug() << "Now follows a breakdown of the jobs.";
3661  kDebug();
3662  for ( int i = 0; i < numberOfPasses; ++i ) {
3663  if ( totalTime[i] == 0 )
3664  continue;
3665  float elementsPerSecond = numElements[i] / ( totalTime[i] / 1000.0f );
3666  float percent = totalTime[i] / (float)totalTotalTime * 100.0f;
3667  kDebug() << "----------------------------------------------";
3668  kDebug() << "Job" << i + 1 << "(" << jobDescription[i] << ")";
3669  kDebug() << "Share of complete time:" << percent << "percent";
3670  kDebug() << "Time in msecs:" << totalTime[i];
3671  kDebug() << "Number of elements:" << numElements[i]; // TODO: map of element string
3672  kDebug() << "Elements per second:" << elementsPerSecond;
3673  kDebug() << "Number of chunks:" << chunks[i];
3674  kDebug();
3675  }
3676 
3677  kDebug() << "==========================================================";
3678  resetStats();
3679 }
3680 
3681 #endif
3682 
3683 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternal()
3684 {
3685  // This function does a timed chunk of work in our View Fill operation.
3686  // It attempts to do processing until it either runs out of jobs
3687  // to be done or a timeout forces it to interrupt and jump back to the caller.
3688 
3689  QTime tStart = QTime::currentTime();
3690  int elapsed;
3691 
3692  while( !mViewItemJobs.isEmpty() )
3693  {
3694  // Have a job to do.
3695  ViewItemJob * job = mViewItemJobs.first();
3696 
3697 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3698 
3699  // Here we check if an old job has just completed or if we are at the start of the
3700  // first job. We then initialize job data stuff and timers based on this.
3701 
3702  const int currentPass = job->currentPass();
3703  const bool firstChunk = currentPass != Stats::lastPass;
3704  if ( currentPass != Stats::lastPass && Stats::lastPass != -1 ) {
3705  Stats::totalTime[Stats::lastPass] = Stats::currentJobStartTime.elapsed();
3706  }
3707  const bool firstJob = job->currentPass() == ViewItemJob::Pass1Fill && firstChunk;
3708  const int elements = job->endIndex() - job->startIndex();
3709  if ( firstJob ) {
3710  Stats::resetStats();
3711  Stats::totalMessages = elements;
3712  Stats::firstStartTime.restart();
3713  }
3714  if ( firstChunk ) {
3715  Stats::numElements[currentPass] = elements;
3716  Stats::currentJobStartTime.restart();
3717  }
3718  Stats::chunks[currentPass]++;
3719  Stats::lastPass = currentPass;
3720 
3721 #endif
3722 
3723  mViewItemJobStepIdleInterval = job->idleInterval();
3724  mViewItemJobStepChunkTimeout = job->chunkTimeout();
3725  mViewItemJobStepMessageCheckCount = job->messageCheckCount();
3726 
3727  if ( job->disconnectUI() )
3728  {
3729  mModelForItemFunctions = 0; // disconnect the UI for this job
3730  Q_ASSERT( mLoading ); // this must be true in the first job
3731  // FIXME: Should assert yet more that this is the very first job for this StorageModel
3732  // Asserting only mLoading is not enough as we could be using a two-jobs loading strategy
3733  // or this could be a job enqueued before the first job has completed.
3734  } else {
3735  // With a connected UI we need to avoid the view to update the scrollbars at EVERY insertion or expansion.
3736  // QTreeViewPrivate::updateScrollBars() is very expensive as it loops through ALL the items in the view every time.
3737  // We can't disable the function directly as it's hidden in the private data object of QTreeView
3738  // but we can disable the parent QTreeView::updateGeometries() instead.
3739  // We will trigger it "manually" at the end of the step.
3740  mView->ignoreUpdateGeometries( true );
3741 
3742  // Ok.. I know that this seems unbelieveable but disabling updates actually
3743  // causes a (significant) performance loss in most cases. This is probably because QTreeView
3744  // uses delayed layouts when updates are disabled which should be delayed but in
3745  // fact are "forced" by next item insertions. The delayed layout algorithm, then
3746  // is probably slower than the non-delayed one.
3747  // Disabling the paintEvent() doesn't seem to work either.
3748  //mView->setUpdatesEnabled( false );
3749  }
3750 
3751  switch( viewItemJobStepInternalForJob( job, tStart ) )
3752  {
3753  case ViewItemJobInterrupted:
3754  {
3755  // current job interrupted by timeout: will propagate status to caller
3756  // but before this, give some feedback to the user
3757 
3758  // FIXME: This is now inaccurate, think of something else
3759  switch( job->currentPass() )
3760  {
3761  case ViewItemJob::Pass1Fill:
3762  case ViewItemJob::Pass1Cleanup:
3763  case ViewItemJob::Pass1Update:
3764  emit q->statusMessage( i18np( "Processed 1 Message of %2",
3765  "Processed %1 Messages of %2",
3766  job->currentIndex() - job->startIndex(),
3767  job->endIndex() - job->startIndex() + 1 ) );
3768  break;
3769  case ViewItemJob::Pass2:
3770  emit q->statusMessage( i18np( "Threaded 1 Message of %2",
3771  "Threaded %1 Messages of %2",
3772  job->currentIndex() - job->startIndex(),
3773  job->endIndex() - job->startIndex() + 1 ) );
3774  break;
3775  case ViewItemJob::Pass3:
3776  emit q->statusMessage( i18np( "Threaded 1 Message of %2",
3777  "Threaded %1 Messages of %2",
3778  job->currentIndex() - job->startIndex(),
3779  job->endIndex() - job->startIndex() + 1 ) );
3780  break;
3781  case ViewItemJob::Pass4:
3782  emit q->statusMessage( i18np( "Grouped 1 Thread of %2",
3783  "Grouped %1 Threads of %2",
3784  job->currentIndex() - job->startIndex(),
3785  job->endIndex() - job->startIndex() + 1 ) );
3786  break;
3787  case ViewItemJob::Pass5:
3788  emit q->statusMessage( i18np( "Updated 1 Group of %2",
3789  "Updated %1 Groups of %2",
3790  job->currentIndex() - job->startIndex(),
3791  job->endIndex() - job->startIndex() + 1 ) );
3792  break;
3793  default: break;
3794  }
3795 
3796  if( !job->disconnectUI() )
3797  {
3798  mView->ignoreUpdateGeometries( false );
3799  // explicit call to updateGeometries() here
3800  mView->updateGeometries();
3801  }
3802 
3803  return ViewItemJobInterrupted;
3804  }
3805  break;
3806  case ViewItemJobCompleted:
3807 
3808  // If this job worked with a disconnected UI, emit layoutChanged()
3809  // to reconnect it. We go back to normal operation now.
3810  if ( job->disconnectUI() )
3811  {
3812  mModelForItemFunctions = q;
3813  // This call would destroy the expanded state of items.
3814  // This is why when mModelForItemFunctions was 0 we didn't actually expand them
3815  // but we just set a "ExpandNeeded" mark...
3816 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3817  QTime layoutChangedTimer;
3818  layoutChangedTimer.start();
3819 #endif
3820  mView->modelAboutToEmitLayoutChanged();
3821  emit q->layoutChanged();
3822  mView->modelEmittedLayoutChanged();
3823 
3824 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3825  Stats::layoutChangeTime = layoutChangedTimer.elapsed();
3826  QTime expandingTime;
3827  expandingTime.start();
3828 #endif
3829 
3830  // expand all the items that need it in a single sweep
3831 
3832  // FIXME: This takes quite a lot of time, it could be made an interruptible job
3833 
3834  QList< Item * > * rootChildItems = mRootItem->childItems();
3835  if ( rootChildItems )
3836  {
3837  QList< Item * >::ConstIterator end( rootChildItems->constEnd() );
3838  for ( QList< Item * >::ConstIterator it = rootChildItems->constBegin(); it != end ;++it )
3839  {
3840  if ( ( *it )->initialExpandStatus() == Item::ExpandNeeded )
3841  syncExpandedStateOfSubtree( *it );
3842  }
3843  }
3844 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3845  Stats::expandingTreeTime = expandingTime.elapsed();
3846 #endif
3847  } else {
3848  mView->ignoreUpdateGeometries( false );
3849  // explicit call to updateGeometries() here
3850  mView->updateGeometries();
3851  }
3852 
3853  // this job has been completed
3854  delete mViewItemJobs.takeFirst();
3855 
3856 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3857  // Last job finished!
3858  Stats::totalTime[currentPass] = Stats::currentJobStartTime.elapsed();
3859  printStatistics();
3860 #endif
3861 
3862  // take care of small jobs which never timeout by themselves because
3863  // of a small number of messages. At the end of each job check
3864  // the time used and if we're timeoutting and there is another job
3865  // then interrupt.
3866  elapsed = tStart.msecsTo( QTime::currentTime() );
3867  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3868  {
3869  if ( !mViewItemJobs.isEmpty() )
3870  return ViewItemJobInterrupted;
3871  // else it's completed in fact
3872  } // else proceed with the next job
3873 
3874  break;
3875  default:
3876  // This is *really* a BUG
3877  kWarning() << "ERROR: returned an invalid result";
3878  Q_ASSERT( false );
3879  break;
3880  }
3881  }
3882 
3883  // no more jobs
3884 
3885  emit q->statusMessage( i18nc( "@info:status Finished view fill", "Ready" ) );
3886 
3887  return ViewItemJobCompleted;
3888 }
3889 
3890 
3891 void ModelPrivate::viewItemJobStep()
3892 {
3893  // A single step in the View Fill operation.
3894  // This function wraps viewItemJobStepInternal() which does the step job
3895  // and either completes it or stops because of a timeout.
3896  // If the job is stopped then we start a zero-msecs timer to call us
3897  // back and resume the job. Otherwise we're just done.
3898 
3899  mViewItemJobStepStartTime = ::time( 0 );
3900 
3901  if( mFillStepTimer.isActive() )
3902  mFillStepTimer.stop();
3903 
3904  if ( !mStorageModel )
3905  return; // nothing more to do
3906 
3907 
3908  // Save the current item in the view as our process may
3909  // cause items to be reparented (and QTreeView will forget the current item in the meantime).
3910  // This machinery is also needed when we're about to remove items from the view in
3911  // a cleanup job: we'll be trying to set as current the item after the one removed.
3912 
3913  QModelIndex currentIndexBeforeStep = mView->currentIndex();
3914  Item * currentItemBeforeStep = currentIndexBeforeStep.isValid() ?
3915  static_cast< Item * >( currentIndexBeforeStep.internalPointer() ) : 0;
3916 
3917  // mCurrentItemToRestoreAfterViewItemJobStep will be zeroed out if it's killed
3918  mCurrentItemToRestoreAfterViewItemJobStep = currentItemBeforeStep;
3919 
3920  // Save the current item position in the viewport as QTreeView fails to keep
3921  // the current item in the sample place when items are added or removed...
3922  QRect rectBeforeViewItemJobStep;
3923 
3924  const bool lockView = mView->isScrollingLocked();
3925 
3926  // This is generally SLOW AS HELL... (so we avoid it if we lock the view and thus don't need it)
3927  if ( mCurrentItemToRestoreAfterViewItemJobStep && ( !lockView ) )
3928  rectBeforeViewItemJobStep = mView->visualRect( currentIndexBeforeStep );
3929 
3930  // FIXME: If the current item is NOT in the view, preserve the position
3931  // of the top visible item. This will make the view move yet less.
3932 
3933  // Insulate the View from (very likely spurious) "currentChanged()" signals.
3934  mView->ignoreCurrentChanges( true );
3935 
3936  // And go to real work.
3937  switch( viewItemJobStepInternal() )
3938  {
3939  case ViewItemJobInterrupted:
3940  // Operation timed out, need to resume in a while
3941  if ( !mInLengthyJobBatch )
3942  {
3943  mInLengthyJobBatch = true;
3944  mView->modelJobBatchStarted();
3945  }
3946  mFillStepTimer.start( mViewItemJobStepIdleInterval ); // this is a single shot timer connected to viewItemJobStep()
3947  // and go dealing with current/selection out of the switch.
3948  break;
3949  case ViewItemJobCompleted:
3950  // done :)
3951 
3952  Q_ASSERT( mModelForItemFunctions ); // UI must be no (longer) disconnected in this state
3953 
3954  // Ask the view to remove the eventual busy indications
3955  if ( mInLengthyJobBatch )
3956  {
3957  mInLengthyJobBatch = false;
3958  mView->modelJobBatchTerminated();
3959  }
3960 
3961  if ( mLoading )
3962  {
3963  mLoading = false;
3964  mView->modelFinishedLoading();
3965  }
3966 
3967  // Apply pre-selection, if any
3968  if ( mPreSelectionMode != PreSelectNone )
3969  {
3970  mView->ignoreCurrentChanges( false );
3971 
3972  bool bSelectionDone = false;
3973 
3974  switch( mPreSelectionMode )
3975  {
3976  case PreSelectLastSelected:
3977  // fall down
3978  break;
3979  case PreSelectFirstUnreadCentered:
3980  bSelectionDone = mView->selectFirstMessageItem( MessageTypeUnreadOnly, true ); // center
3981  break;
3982  case PreSelectOldestCentered:
3983  mView->setCurrentMessageItem( mOldestItem, true /* center */ );
3984  bSelectionDone = true;
3985  break;
3986  case PreSelectNewestCentered:
3987  mView->setCurrentMessageItem( mNewestItem, true /* center */ );
3988  bSelectionDone = true;
3989  break;
3990  case PreSelectNone:
3991  // deal with selection below
3992  break;
3993  default:
3994  kWarning() << "ERROR: Unrecognized pre-selection mode " << (int)mPreSelectionMode;
3995  break;
3996  }
3997 
3998  if ( ( !bSelectionDone ) && ( mPreSelectionMode != PreSelectNone ) )
3999  {
4000  // fallback to last selected, if possible
4001  if ( mLastSelectedMessageInFolder ) // we found it in the loading process: select and jump out
4002  {
4003  mView->setCurrentMessageItem( mLastSelectedMessageInFolder );
4004  bSelectionDone = true;
4005  }
4006  }
4007 
4008  if ( bSelectionDone ) {
4009  mLastSelectedMessageInFolder = 0;
4010  mPreSelectionMode = PreSelectNone;
4011  return; // already taken care of current / selection
4012  }
4013  }
4014  // deal with current/selection out of the switch
4015 
4016  break;
4017  default:
4018  // This is *really* a BUG
4019  kWarning() << "ERROR: returned an invalid result";
4020  Q_ASSERT( false );
4021  break;
4022  }
4023 
4024  // Everything else here deals with the selection
4025 
4026  // If UI is disconnected then we don't have anything else to do here
4027  if ( !mModelForItemFunctions )
4028  {
4029  mView->ignoreCurrentChanges( false );
4030  return;
4031  }
4032 
4033  // Restore current/selection and/or scrollbar position
4034 
4035  if ( mCurrentItemToRestoreAfterViewItemJobStep )
4036  {
4037  bool stillIgnoringCurrentChanges = true;
4038 
4039  // If the assert below fails then the previously current item got detached
4040  // and didn't get reattached in the step: this should never happen.
4041  Q_ASSERT( mCurrentItemToRestoreAfterViewItemJobStep->isViewable() );
4042 
4043  // Check if the current item changed
4044  QModelIndex currentIndexAfterStep = mView->currentIndex();
4045  Item * currentAfterStep = currentIndexAfterStep.isValid() ?
4046  static_cast< Item * >( currentIndexAfterStep.internalPointer() ) : 0;
4047 
4048  if ( mCurrentItemToRestoreAfterViewItemJobStep != currentAfterStep )
4049  {
4050  // QTreeView lost the current item...
4051  if ( mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep )
4052  {
4053  // Some view job code expects us to actually *change* the current item.
4054  // This is done by the cleanup step which removes items and tries
4055  // to set as current the item *after* the removed one, if possible.
4056  // We need the view to handle the change though.
4057  stillIgnoringCurrentChanges = false;
4058  mView->ignoreCurrentChanges( false );
4059  } else {
4060  // we just have to restore the old current item. The code
4061  // outside shouldn't have noticed that we lost it (e.g. the message viewer
4062  // still should have the old message opened). So we don't need to
4063  // actually notify the view of the restored setting.
4064  }
4065  // Restore it
4066  kDebug() << "Gonna restore current here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4067  mView->setCurrentIndex( q->index( mCurrentItemToRestoreAfterViewItemJobStep, 0 ) );
4068  } else {
4069  // The item we're expected to set as current is already current
4070  if ( mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep )
4071  {
4072  // But we have changed it in the job step.
4073  // This means that: we have deleted the current item and chosen a
4074  // new candidate as current but Qt also has chosen it as candidate
4075  // and already made it current. The problem is that (as of Qt 4.4)
4076  // it probably didn't select it.
4077  if ( !mView->selectionModel()->hasSelection() )
4078  {
4079  stillIgnoringCurrentChanges = false;
4080  mView->ignoreCurrentChanges( false );
4081 
4082  kDebug() << "Gonna restore selection here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4083 
4084  QItemSelection selection;
4085  selection.append( QItemSelectionRange( q->index( mCurrentItemToRestoreAfterViewItemJobStep, 0 ) ) );
4086  mView->selectionModel()->select( selection, QItemSelectionModel::Select | QItemSelectionModel::Rows );
4087  }
4088  }
4089  }
4090 
4091  // FIXME: If it was selected before the change, then re-select it (it may happen that it's not)
4092  if ( !lockView )
4093  {
4094  // we prefer to keep the currently selected item steady in the view
4095  QRect rectAfterViewItemJobStep = mView->visualRect( q->index( mCurrentItemToRestoreAfterViewItemJobStep, 0 ) );
4096  if ( rectBeforeViewItemJobStep.y() != rectAfterViewItemJobStep.y() )
4097  {
4098  // QTreeView lost its position...
4099  mView->verticalScrollBar()->setValue( mView->verticalScrollBar()->value() + rectAfterViewItemJobStep.y() - rectBeforeViewItemJobStep.y() );
4100  }
4101  }
4102 
4103  // and kill the insulation, if not yet done
4104  if ( stillIgnoringCurrentChanges )
4105  mView->ignoreCurrentChanges( false );
4106 
4107  return;
4108  }
4109 
4110  // Either there was no current item before, or it was lost in a cleanup step and another candidate for
4111  // current item couldn't be found (possibly empty view)
4112  mView->ignoreCurrentChanges( false );
4113 
4114  if ( currentItemBeforeStep )
4115  {
4116  // lost in a cleanup..
4117  // tell the view that we have a new current, this time with no insulation
4118  mView->slotSelectionChanged( QItemSelection(), QItemSelection() );
4119  }
4120 }
4121 
4122 void ModelPrivate::slotStorageModelRowsInserted( const QModelIndex &parent, int from, int to )
4123 {
4124  if ( parent.isValid() )
4125  return; // ugh... should never happen
4126 
4127  Q_ASSERT( from <= to );
4128 
4129  int count = ( to - from ) + 1;
4130 
4131  mInvariantRowMapper->modelRowsInserted( from, count );
4132 
4133  // look if no current job is in the middle
4134 
4135  int jobCount = mViewItemJobs.count();
4136 
4137  for ( int idx = 0; idx < jobCount; idx++ )
4138  {
4139  ViewItemJob * job = mViewItemJobs.at( idx );
4140 
4141  if ( job->currentPass() != ViewItemJob::Pass1Fill )
4142  {
4143  // The job is a cleanup or in a later pass: the storage has been already accessed
4144  // and the messages created... no need to care anymore: the invariant row mapper will do the job.
4145  continue;
4146  }
4147 
4148  if ( job->currentIndex() > job->endIndex() )
4149  {
4150  // The job finished the Pass1Fill but still waits for the pass indicator to be
4151  // changed. This is unlikely but still may happen if the job has been interrupted
4152  // and then a call to slotStorageModelRowsRemoved() caused it to be forcibly completed.
4153  continue;
4154  }
4155 
4156  //
4157  // The following cases are possible:
4158  //
4159  // from to
4160  // | | -> shift up job
4161  // from to
4162  // | | -> shift up job
4163  // from to
4164  // | | -> shift up job
4165  // from to
4166  // | | -> split job
4167  // from to
4168  // | | -> split job
4169  // from to
4170  // | | -> job unaffected
4171  //
4172  //
4173  // FOLDER
4174  // |-------------------------|---------|--------------|
4175  // 0 currentIndex endIndex count
4176  // +-- job --+
4177  //
4178 
4179  if ( from > job->endIndex() )
4180  {
4181  // The change is completely above the job, the job is not affected
4182  continue;
4183  }
4184 
4185  if( from > job->currentIndex() ) // and from <= job->endIndex()
4186  {
4187  // The change starts in the middle of the job in a way that it must be split in two.
4188  // The first part is unaffected by the shift and ranges from job->currentIndex() to from - 1.
4189  // The second part ranges from "from" to job->endIndex() that are now shifted up by count steps.
4190 
4191  // First add a new job for the second part.
4192  ViewItemJob * newJob = new ViewItemJob( from + count, job->endIndex() + count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount() );
4193 
4194  Q_ASSERT( newJob->currentIndex() <= newJob->endIndex() );
4195 
4196  idx++; // we can skip this job in the loop, it's already ok
4197  jobCount++; // and our range increases by one.
4198  mViewItemJobs.insert( idx, newJob );
4199 
4200  // Then limit the original job to the first part
4201  job->setEndIndex( from - 1 );
4202 
4203  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4204 
4205  continue;
4206  }
4207 
4208  // The change starts below (or exactly on the beginning of) the job.
4209  // The job must be shifted up.
4210  job->setCurrentIndex( job->currentIndex() + count );
4211  job->setEndIndex( job->endIndex() + count );
4212 
4213  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4214  }
4215 
4216  bool newJobNeeded = true;
4217 
4218  // Try to attach to an existing fill job, if any.
4219  // To enforce consistency we can attach only if the Fill job
4220  // is the last one in the list (might be eventually *also* the first,
4221  // and even being already processed but we must make sure that there
4222  // aren't jobs _after_ it).
4223  if ( jobCount > 0 )
4224  {
4225  ViewItemJob * job = mViewItemJobs.at( jobCount - 1 );
4226  if ( job->currentPass() == ViewItemJob::Pass1Fill )
4227  {
4228  if (
4229  // The job ends just before the added rows
4230  ( from == ( job->endIndex() + 1 ) ) &&
4231  // The job didn't reach the end of Pass1Fill yet
4232  ( job->currentIndex() <= job->endIndex() )
4233  )
4234  {
4235  // We can still attach this :)
4236  job->setEndIndex( to );
4237  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4238  newJobNeeded = false;
4239  }
4240  }
4241  }
4242 
4243  if ( newJobNeeded )
4244  {
4245  // FIXME: Should take timing options from aggregation here ?
4246  ViewItemJob * job = new ViewItemJob( from, to, 100, 50, 10 );
4247  mViewItemJobs.append( job );
4248  }
4249 
4250  if ( !mFillStepTimer.isActive() )
4251  mFillStepTimer.start( mViewItemJobStepIdleInterval );
4252 }
4253 
4254 void ModelPrivate::slotStorageModelRowsRemoved( const QModelIndex &parent, int from, int to )
4255 {
4256  // This is called when the underlying StorageModel emits the rowsRemoved signal.
4257 
4258  if ( parent.isValid() )
4259  return; // ugh... should never happen
4260 
4261  // look if no current job is in the middle
4262 
4263  Q_ASSERT( from <= to );
4264 
4265  const int count = ( to - from ) + 1;
4266 
4267  int jobCount = mViewItemJobs.count();
4268 
4269  if (mRootItem && from == 0 && count == mRootItem->childItemCount() && jobCount == 0) {
4270  clear();
4271  return;
4272  }
4273 
4274  for ( int idx = 0; idx < jobCount; idx++ )
4275  {
4276  ViewItemJob * job = mViewItemJobs.at( idx );
4277 
4278  if ( job->currentPass() != ViewItemJob::Pass1Fill )
4279  {
4280  // The job is a cleanup or in a later pass: the storage has been already accessed
4281  // and the messages created... no need to care: we will invalidate the messages in a while.
4282  continue;
4283  }
4284 
4285  if ( job->currentIndex() > job->endIndex() )
4286  {
4287  // The job finished the Pass1Fill but still waits for the pass indicator to be
4288  // changed. This is unlikely but still may happen if the job has been interrupted
4289  // and then a call to slotStorageModelRowsRemoved() caused it to be forcibly completed.
4290  continue;
4291  }
4292 
4293  //
4294  // The following cases are possible:
4295  //
4296  // from to
4297  // | | -> shift down job
4298  // from to
4299  // | | -> shift down and crop job
4300  // from to
4301  // | | -> kill job
4302  // from to
4303  // | | -> split job, crop and shift
4304  // from to
4305  // | | -> crop job
4306  // from to
4307  // | | -> job unaffected
4308  //
4309  //
4310  // FOLDER
4311  // |-------------------------|---------|--------------|
4312  // 0 currentIndex endIndex count
4313  // +-- job --+
4314  //
4315 
4316  if ( from > job->endIndex() )
4317  {
4318  // The change is completely above the job, the job is not affected
4319  continue;
4320  }
4321 
4322  if( from > job->currentIndex() ) // and from <= job->endIndex()
4323  {
4324  // The change starts in the middle of the job and ends in the middle or after the job.
4325  // The first part is unaffected by the shift and ranges from job->currentIndex() to from - 1
4326  // We use the existing job for this.
4327  job->setEndIndex( from - 1 ); // stop before the first removed row
4328 
4329  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4330 
4331  if ( to < job->endIndex() )
4332  {
4333  // The change ends inside the job and a part of it can be completed.
4334 
4335  // We create a new job for the shifted remaining part. It would actually
4336  // range from to + 1 up to job->endIndex(), but we need to shift it down by count.
4337  // since count = ( to - from ) + 1 so from = to + 1 - count
4338 
4339  ViewItemJob * newJob = new ViewItemJob( from, job->endIndex() - count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount() );
4340 
4341  Q_ASSERT( newJob->currentIndex() < newJob->endIndex() );
4342 
4343  idx++; // we can skip this job in the loop, it's already ok
4344  jobCount++; // and our range increases by one.
4345  mViewItemJobs.insert( idx, newJob );
4346  } // else the change includes completely the end of the job and no other part of it can be completed.
4347 
4348  continue;
4349  }
4350 
4351  // The change starts below (or exactly on the beginning of) the job. ( from <= job->currentIndex() )
4352  if ( to >= job->endIndex() )
4353  {
4354  // The change completely covers the job: kill it
4355 
4356  // We don't delete the job since we want the other passes to be completed
4357  // This is because the Pass1Fill may have already filled mUnassignedMessageListForPass2
4358  // and may have set mOldestItem and mNewestItem. We *COULD* clear the unassigned
4359  // message list with clearUnassignedMessageLists() but mOldestItem and mNewestItem
4360  // could be still dangling pointers. So we just move the current index of the job
4361  // after the end (so storage model scan terminates) and let it complete spontaneously.
4362  job->setCurrentIndex( job->endIndex() + 1 );
4363 
4364  continue;
4365  }
4366 
4367  if ( to >= job->currentIndex() )
4368  {
4369  // The change partially covers the job. Only a part of it can be completed
4370  // and it must be shifted down. It would actually
4371  // range from to + 1 up to job->endIndex(), but we need to shift it down by count.
4372  // since count = ( to - from ) + 1 so from = to + 1 - count
4373  job->setCurrentIndex( from );
4374  job->setEndIndex( job->endIndex() - count );
4375 
4376  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4377 
4378  continue;
4379  }
4380 
4381  // The change is completely below the job: it must be shifted down.
4382  job->setCurrentIndex( job->currentIndex() - count );
4383  job->setEndIndex( job->endIndex() - count );
4384  }
4385 
4386  // This will invalidate the ModelInvariantIndex-es that have been removed and return
4387  // them all in a nice list that we can feed to a view removal job.
4388  QList< ModelInvariantIndex * > * invalidatedIndexes = mInvariantRowMapper->modelRowsRemoved( from, count );
4389 
4390  if ( invalidatedIndexes )
4391  {
4392  // Try to attach to an existing cleanup job, if any.
4393  // To enforce consistency we can attach only if the Cleanup job
4394  // is the last one in the list (might be eventually *also* the first,
4395  // and even being already processed but we must make sure that there
4396  // aren't jobs _after_ it).
4397  if ( jobCount > 0 )
4398  {
4399  ViewItemJob * job = mViewItemJobs.at( jobCount - 1 );
4400  if ( job->currentPass() == ViewItemJob::Pass1Cleanup )
4401  {
4402  if ( ( job->currentIndex() <= job->endIndex() ) && job->invariantIndexList() )
4403  {
4404  //kDebug() << "Appending " << invalidatedIndexes->count() << " invalidated indexes to existing cleanup job" << endl;
4405  // We can still attach this :)
4406  *( job->invariantIndexList() ) += *invalidatedIndexes;
4407  job->setEndIndex( job->endIndex() + invalidatedIndexes->count() );
4408  delete invalidatedIndexes;
4409  invalidatedIndexes = 0;
4410  }
4411  }
4412  }
4413 
4414  if ( invalidatedIndexes )
4415  {
4416  // Didn't append to any existing cleanup job.. create a new one
4417 
4418  //kDebug() << "Creating new cleanup job for " << invalidatedIndexes->count() << " invalidated indexes" << endl;
4419  // FIXME: Should take timing options from aggregation here ?
4420  ViewItemJob * job = new ViewItemJob( ViewItemJob::Pass1Cleanup, invalidatedIndexes, 100, 50, 10 );
4421  mViewItemJobs.append( job );
4422  }
4423 
4424  if ( !mFillStepTimer.isActive() )
4425  mFillStepTimer.start( mViewItemJobStepIdleInterval );
4426  }
4427 }
4428 
4429 void ModelPrivate::slotStorageModelLayoutChanged()
4430 {
4431  kDebug() << "Storage model layout changed";
4432  // need to reset everything...
4433  q->setStorageModel( mStorageModel );
4434  kDebug() << "Storage model layout changed done";
4435 }
4436 
4437 void ModelPrivate::slotStorageModelDataChanged( const QModelIndex &fromIndex, const QModelIndex &toIndex )
4438 {
4439  Q_ASSERT( mStorageModel ); // must exist (and be the sender of the signal connected to this slot)
4440 
4441  int from = fromIndex.row();
4442  int to = toIndex.row();
4443 
4444  Q_ASSERT( from <= to );
4445 
4446  int count = ( to - from ) + 1;
4447 
4448  int jobCount = mViewItemJobs.count();
4449 
4450  // This will find out the ModelInvariantIndex-es that need an update and will return
4451  // them all in a nice list that we can feed to a view removal job.
4452  QList< ModelInvariantIndex * > * indexesThatNeedUpdate = mInvariantRowMapper->modelIndexRowRangeToModelInvariantIndexList( from, count );
4453 
4454  if ( indexesThatNeedUpdate )
4455  {
4456  // Try to attach to an existing update job, if any.
4457  // To enforce consistency we can attach only if the Update job
4458  // is the last one in the list (might be eventually *also* the first,
4459  // and even being already processed but we must make sure that there
4460  // aren't jobs _after_ it).
4461  if ( jobCount > 0 )
4462  {
4463  ViewItemJob * job = mViewItemJobs.at( jobCount - 1 );
4464  if ( job->currentPass() == ViewItemJob::Pass1Update )
4465  {
4466  if ( ( job->currentIndex() <= job->endIndex() ) && job->invariantIndexList() )
4467  {
4468  // We can still attach this :)
4469  *( job->invariantIndexList() ) += *indexesThatNeedUpdate;
4470  job->setEndIndex( job->endIndex() + indexesThatNeedUpdate->count() );
4471  delete indexesThatNeedUpdate;
4472  indexesThatNeedUpdate = 0;
4473  }
4474  }
4475  }
4476 
4477  if ( indexesThatNeedUpdate )
4478  {
4479  // Didn't append to any existing update job.. create a new one
4480  // FIXME: Should take timing options from aggregation here ?
4481  ViewItemJob * job = new ViewItemJob( ViewItemJob::Pass1Update, indexesThatNeedUpdate, 100, 50, 10 );
4482  mViewItemJobs.append( job );
4483  }
4484 
4485  if ( !mFillStepTimer.isActive() )
4486  mFillStepTimer.start( mViewItemJobStepIdleInterval );
4487  }
4488 
4489 }
4490 
4491 void ModelPrivate::slotStorageModelHeaderDataChanged( Qt::Orientation, int, int )
4492 {
4493  if ( mStorageModelContainsOutboundMessages!=mStorageModel->containsOutboundMessages() ) {
4494  mStorageModelContainsOutboundMessages = mStorageModel->containsOutboundMessages();
4495  emit q->headerDataChanged( Qt::Horizontal, 0, q->columnCount() );
4496  }
4497 }
4498 
4499 Qt::ItemFlags Model::flags( const QModelIndex &index ) const
4500 {
4501  if ( !index.isValid() )
4502  return Qt::NoItemFlags;
4503 
4504  Q_ASSERT( d->mModelForItemFunctions ); // UI must be connected if a valid index was queried
4505 
4506  Item * it = static_cast< Item * >( index.internalPointer() );
4507 
4508  Q_ASSERT( it );
4509 
4510  if ( it->type() == Item::GroupHeader )
4511  return Qt::ItemIsEnabled;
4512 
4513  Q_ASSERT( it->type() == Item::Message );
4514 
4515  if ( !static_cast< MessageItem * >( it )->isValid() )
4516  return Qt::NoItemFlags; // not enabled, not selectable
4517 
4518  if ( static_cast< MessageItem * >( it )->aboutToBeRemoved() )
4519  return Qt::NoItemFlags; // not enabled, not selectable
4520 
4521  if ( static_cast< MessageItem * >( it )->status().isDeleted() )
4522  return Qt::NoItemFlags; // not enabled, not selectable
4523 
4524  return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
4525 }
4526 
4527 QMimeData* MessageList::Core::Model::mimeData( const QModelIndexList& indexes ) const
4528 {
4529  QList< MessageItem* > msgs;
4530  foreach( const QModelIndex &idx, indexes ) {
4531  if( idx.isValid() ) {
4532  Item* item = static_cast< Item* >( idx.internalPointer() );
4533  if( item->type() == MessageList::Core::Item::Message ) {
4534  msgs << static_cast< MessageItem* >( idx.internalPointer() );
4535  }
4536  }
4537  }
4538  return storageModel()->mimeData( msgs );
4539 }
4540 
4541 
4542 Item *Model::rootItem() const
4543 {
4544  return d->mRootItem;
4545 }
4546 
4547 bool Model::isLoading() const
4548 {
4549  return d->mLoading;
4550 }
4551 
4552 MessageItem * Model::messageItemByStorageRow( int row ) const
4553 {
4554  if ( !d->mStorageModel )
4555  return 0;
4556  ModelInvariantIndex * idx = d->mInvariantRowMapper->modelIndexRowToModelInvariantIndex( row );
4557  if ( !idx )
4558  return 0;
4559 
4560  return static_cast< MessageItem * >( idx );
4561 }
4562 
4563 
4564 MessageItemSetReference Model::createPersistentSet( const QList< MessageItem * > &items )
4565 {
4566  if ( !d->mPersistentSetManager )
4567  d->mPersistentSetManager = new MessageItemSetManager();
4568 
4569  MessageItemSetReference ref = d->mPersistentSetManager->createSet();
4570  QList< MessageItem * >::ConstIterator end = items.constEnd();
4571  for ( QList< MessageItem * >::ConstIterator it = items.constBegin(); it != end; ++it )
4572  d->mPersistentSetManager->addMessageItem( ref, *it );
4573 
4574  return ref;
4575 }
4576 
4577 QList< MessageItem * > Model::persistentSetCurrentMessageItemList( MessageItemSetReference ref )
4578 {
4579  if ( d->mPersistentSetManager )
4580  return d->mPersistentSetManager->messageItems( ref );
4581  return QList< MessageItem * >();
4582 
4583 }
4584 
4585 void Model::deletePersistentSet( MessageItemSetReference ref )
4586 {
4587  if ( !d->mPersistentSetManager )
4588  return;
4589 
4590  d->mPersistentSetManager->removeSet( ref );
4591 
4592  if ( d->mPersistentSetManager->setCount() < 1 )
4593  {
4594  delete d->mPersistentSetManager;
4595  d->mPersistentSetManager = 0;
4596  }
4597 }
4598 
4599 #include "moc_model.cpp"
MessageList::Core::ModelPrivate::clearOrphanChildrenHash
void clearOrphanChildrenHash()
Definition: model.cpp:1054
QObject::child
QObject * child(const char *objName, const char *inheritsClass, bool recursiveSearch) const
MessageList::Core::ItemPrivate::childItemNeedsReSorting
bool childItemNeedsReSorting(Item *child)
Checks if the specified child item is actually in the wrong position in the child list and returns tr...
Definition: item_p.h:191
MessageList::Core::SortOrder::SortMessagesByDateTimeOfMostRecent
Sort the messages by date and time of the most recent message in subtree.
Definition: sortorder.h:82
MessageList::Core::ModelPrivate::mCachedTwoWeeksAgoLabel
QString mCachedTwoWeeksAgoLabel
The label for the "Two Weeks Ago" group item, cached, so we don't translate it multiple times...
Definition: model_p.h:328
MessageList::Core::Model::setPreSelectionMode
void setPreSelectionMode(PreSelectionMode preSelect)
Sets the pre-selection mode.
Definition: model.cpp:902
QDate::daysTo
int daysTo(const QDate &d) const
MessageList::Core::Aggregation::PerfectAndReferences
Thread by "In-Reply-To" and "References" fields.
Definition: aggregation.h:87
QList::clear
void clear()
MessageList::Core::ModelPrivate::mGroupHeadersThatNeedUpdate
QHash< GroupHeaderItem *, GroupHeaderItem * > mGroupHeadersThatNeedUpdate
List of group headers that either need to be re-sorted or must be removed because empty...
Definition: model_p.h:231
QModelIndex
MessageList::Core::ModelPrivate::mThreadingCacheMessageInReplyToIdMD5ToMessageItem
QMultiHash< QByteArray, MessageItem * > mThreadingCacheMessageInReplyToIdMD5ToMessageItem
Threading cache.
Definition: model_p.h:220
MessageList::Core::SortOrder
A class which holds information about sorting, e.g.
Definition: sortorder.h:37
MessageList::Core::SortOrder::SortMessagesBySubject
Sort the messages by subject.
Definition: sortorder.h:86
QAbstractItemModel::rowCount
virtual int rowCount(const QModelIndex &parent) const =0
MessageList::Core::ModelPrivate::handleItemPropertyChanges
bool handleItemPropertyChanges(int propertyChangeMask, Item *parent, Item *item)
Handle the specified property changes in item.
Definition: model.cpp:1715
MessageList::Core::ModelInvariantRowMapper::modelReset
void modelReset()
Call this function from your handlers of reset() and layoutChanged() AFTER you ve last accessed the m...
Definition: modelinvariantrowmapper.cpp:576
MessageList::Core::ModelPrivate::ImportantStatusChanged
Definition: model_p.h:119
MessageList::Core::ModelPrivate::mRootItem
Item * mRootItem
Owned invisible root item, useful to implement algorithms that not need to handle the special case of...
Definition: model_p.h:268
MessageList::Core::Aggregation
A set of aggregation options that can be applied to the MessageList::Model in a single shot...
Definition: aggregation.h:43
MessageList::Core::GroupHeaderItem
Definition: groupheaderitem.h:34
QHash::insert
iterator insert(const Key &key, const T &value)
MessageList::Core::View::messageItemBefore
Item * messageItemBefore(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
Finds message item that comes "before" the reference item.
Definition: view.cpp:1251
MessageList::Core::View::ignoreCurrentChanges
void ignoreCurrentChanges(bool ignore)
This is called by the model to insulate us from certain QTreeView signals This is because they may be...
Definition: view.cpp:162
MessageList::Core::Item::ExpandNeeded
Must expand when this item becomes viewable.
Definition: item.h:75
delegate.h
MessageList::Core::MessageItemSetManager
This class manages sets of messageitem references.
Definition: messageitemsetmanager.h:48
MessageList::Core::ModelPrivate::mStorageModelContainsOutboundMessages
bool mStorageModelContainsOutboundMessages
The cached result of StorageModel::containsOutboundMessages().
Definition: model_p.h:423
QItemSelectionRange
MessageList::Core::SortOrder::SortGroupsByDateTime
Sort groups by date/time of the group.
Definition: sortorder.h:54
QAbstractItemView::setCurrentIndex
void setCurrentIndex(const QModelIndex &index)
QAbstractItemModel::layoutChanged
void layoutChanged()
MessageList::Core::Model::statusMessage
void statusMessage(const QString &message)
Notify the outside when updating the status bar with a message could be useful.
MessageList::Core::ModelPrivate::mCachedUnknownLabel
QString mCachedUnknownLabel
The label for the "Unknown" group item, cached, so we don't translate it multiple times...
Definition: model_p.h:318
MessageList::Core::ModelPrivate::mModelForItemFunctions
Model * mModelForItemFunctions
This pointer is passed to the Item functions that insert children.
Definition: model_p.h:416
MessageList::Core::Aggregation::threadLeader
ThreadLeader threadLeader() const
Returns the current thread leader determination method.
Definition: aggregation.h:221
MessageList::Core::ModelPrivate::ViewItemJobCompleted
Definition: model_p.h:71
MessageList::Core::Aggregation::PerfectReferencesAndSubject
Thread by all of the above and try to match subjects too.
Definition: aggregation.h:88
QByteArray
MessageList::Core::MessageItem
Definition: messageitem.h:46
QAbstractItemView::selectionModel
QItemSelectionModel * selectionModel() const
MessageList::Core::ModelPrivate::mUnassignedMessageListForPass4
QList< MessageItem * > mUnassignedMessageListForPass4
List of unassigned messages, used to handle threading in two passes, pointers are owned! ...
Definition: model_p.h:246
MessageList::Core::GroupHeaderItem::label
const QString & label() const
Definition: groupheaderitem.cpp:35
MessageList::Core::MessageItem::setThreadingStatus
void setThreadingStatus(ThreadingStatus threadingStatus)
Definition: messageitem.cpp:528
MessageList::Core::Aggregation::NoThreading
Perform no threading at all.
Definition: aggregation.h:85
MessageList::Core::Item::isViewable
bool isViewable() const
Is this item attached to the viewable root ?
Definition: item.cpp:312
MessageList::Core::ModelPrivate::guessMessageParent
MessageItem * guessMessageParent(MessageItem *mi)
Attempt to find the threading parent for the specified message item.
Definition: model.cpp:1603
MessageList::Core::Item::date
time_t date() const
Returns the date of this item.
Definition: item.cpp:421
MessageList::Core::ModelPrivate::mViewItemJobs
QList< ViewItemJob * > mViewItemJobs
Pending fill view jobs, pointers are owned.
Definition: model_p.h:256
filter.h
MessageList::Core::Item::senderOrReceiver
const QString & senderOrReceiver() const
Returns the sender or the receiver, depending on the underlying StorageModel settings.
Definition: item.cpp:461
MessageList::Core::MessageItem::topmostMessage
MessageItem * topmostMessage()
Definition: messageitem.cpp:552
MessageList::Core::Aggregation::grouping
Grouping grouping() const
Returns the currently set Grouping option.
Definition: aggregation.h:162
MessageList::Core::Aggregation::GroupBySender
Group by sender, always.
Definition: aggregation.h:58
MessageList::Core::Aggregation::ExpandRecentGroups
Makes sense only with GroupByDate or GroupByDateRange.
Definition: aggregation.h:73
MessageList::Core::Model::~Model
~Model()
Destroys the mighty model along with the tree of items it manages.
Definition: model.cpp:310
MessageList::Core::ModelPrivate::mCachedWatchedOrIgnoredStatusBits
qint32 mCachedWatchedOrIgnoredStatusBits
Cached bits that we use for fast status checks.
Definition: model_p.h:348
MessageList::Core::Theme::Column::pixmapName
const QString & pixmapName() const
Returns the icon's name (used in SmallIcon) set for this column.
Definition: theme.h:687
QList::at
const T & at(int i) const
MessageList::Core::ModelPrivate::mFilter
const Filter * mFilter
The filter to apply on messages.
Definition: model_p.h:198
MessageList::Core::ModelPrivate::mOldestItem
MessageItem * mOldestItem
Definition: model_p.h:386
MessageList::Core::Aggregation::threading
Threading threading() const
Returns the current threading method.
Definition: aggregation.h:202
MessageList::Core::MessageTypeAny
Definition: enums.h:58
MessageList::Core::StorageModel::initialUnreadRowCountGuess
virtual int initialUnreadRowCountGuess() const
Returns (a guess for) the number of unread messages: must be pessimistic (i.e.
Definition: storagemodelbase.cpp:36
QByteArray::isEmpty
bool isEmpty() const
MessageList::Core::ModelPrivate::slotStorageModelLayoutChanged
void slotStorageModelLayoutChanged()
Definition: model.cpp:4429
MessageList::Core::ModelPrivate::mCachedFourWeeksAgoLabel
QString mCachedFourWeeksAgoLabel
The label for the "Four Weeks Ago" group item, cached, so we don't translate it multiple times...
Definition: model_p.h:338
MessageList::Core::Aggregation::GroupByDate
Group the messages by the date of the thread leader.
Definition: aggregation.h:55
MessageList::Core::Model::rootItem
Item * rootItem() const
Returns the hidden root item that all the messages are (or will be) attached to.
Definition: model.cpp:4542
MessageList::Core::Item::childItemCount
int childItemCount() const
Returns the number of children of this Item.
Definition: item.cpp:163
MessageList::Core::Item::NoExpandNeeded
No expand needed at all.
Definition: item.h:76
MessageList::Core::ModelPrivate::syncExpandedStateOfSubtree
void syncExpandedStateOfSubtree(Item *root)
Sync the expanded state of the subtree with the specified root.
Definition: model.cpp:1184
MessageList::Core::Aggregation::PerfectOnly
Thread by "In-Reply-To" field only.
Definition: aggregation.h:86
QTime::msecsTo
int msecsTo(const QTime &t) const
MessageList::Core::ItemPrivate
Definition: item_p.h:41
MessageList::Core::View::modelJobBatchTerminated
void modelJobBatchTerminated()
This is called by Model to signal the end of a lengthy job batch.
Definition: view.cpp:286
MessageList::Core::Model::messageItemByStorageRow
MessageItem * messageItemByStorageRow(int row) const
Returns the message item that is at the current storage row index or zero if no such storage item is ...
Definition: model.cpp:4552
MessageList::Core::Model::rowCount
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const
Definition: model.cpp:590
MessageList::Core::SortOrder::SortGroupsByReceiver
Sort groups by receiver (makes sense only with GroupByReceiver)
Definition: sortorder.h:58
QList::erase
iterator erase(iterator pos)
MessageList::Core::Item::receiver
const QString & receiver() const
Returns the receiver associated to this item.
Definition: item.cpp:451
MessageList::Core::Aggregation::NeverExpandGroups
Never expand groups during a view fill algorithm.
Definition: aggregation.h:72
MessageList::Core::Aggregation::GroupByDateRange
Use smart (thread leader) date ranges ("Today","Yesterday","Last Week"...)
Definition: aggregation.h:56
model.h
MessageList::Core::Item::setViewable
void setViewable(Model *model, bool bViewable)
Makes this item viewable, that is, notifies its existence to any listener attacched to the "rowsInser...
Definition: item.cpp:322
QRect::y
int y() const
theme.h
MessageList::Core::ModelInvariantRowMapper
This class is an optimizing helper for dealing with large flat QAbstractItemModel objects...
Definition: modelinvariantrowmapper.h:93
MessageList::Core::ModelPrivate::attachMessageToParent
void attachMessageToParent(Item *pParent, MessageItem *mi)
Definition: model.cpp:2001
QAbstractItemModel::modelReset
void modelReset()
MessageList::Core::View::isScrollingLocked
bool isScrollingLocked() const
Returns true if the vertical scrollbar should keep to the top or bottom while inserting items...
Definition: view.cpp:181
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass5
ViewItemJobResult viewItemJobStepInternalForJobPass5(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2311
MessageList::Core::ItemMaxDateComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:302
MessageList::Core::MessageItemSetManager::removeMessageItemFromAllSets
void removeMessageItemFromAllSets(MessageItem *mi)
Definition: messageitemsetmanager.cpp:75
MessageList::Core::Item::subject
const QString & subject() const
Returns the subject associated to this Item.
Definition: item.cpp:471
QTreeView::visualRect
virtual QRect visualRect(const QModelIndex &index) const
MessageList::Core::ItemAttachmentStatusComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:468
MessageList::Core::Item::size
size_t size() const
Returns the size of this item (size of the Message, mainly)
Definition: item.cpp:411
MessageList::Core::View
The MessageList::View is the real display of the message list.
Definition: view.h:65
MessageList::Core::MessageItem::ParentMissing
this message might belong to a thread but its parent is actually missing
Definition: messageitem.h:76
MessageList::Core::ItemSenderComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:339
QObject::disconnect
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
MessageList::Core::View::messageItemAfter
Item * messageItemAfter(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
Finds message item that comes "after" the reference item.
Definition: view.cpp:1121
QTime
MessageList::Core::ModelPrivate::mViewItemJobStepChunkTimeout
int mViewItemJobStepChunkTimeout
The timeout for a single ViewItemJob step.
Definition: model_p.h:284
MessageList::Core::PreSelectFirstUnreadCentered
Definition: enums.h:48
MessageList::Core::Theme::Column::isSenderOrReceiver
bool isSenderOrReceiver() const
Returns true if this column is marked as "sender/receiver" and we should update its label on-the-fly...
Definition: theme.h:700
MessageList::Core::MessageItem::ImperfectParentFound
this message found an imperfect parent to attach to (might be fixed later)
Definition: messageitem.h:75
QMimeData
MessageList::Core::ModelPrivate::mThreadingCacheMessageIdMD5ToMessageItem
QHash< QByteArray, MessageItem * > mThreadingCacheMessageIdMD5ToMessageItem
Threading cache.
Definition: model_p.h:214
MessageList::Core::Item::recomputeMaxDate
bool recomputeMaxDate()
Recompute the maximum date from the current children list.
Definition: item.cpp:275
MessageList::Core::Aggregation::NoGrouping
Don't group messages at all.
Definition: aggregation.h:54
MessageList::Core::ItemPrivate::mType
Item::Type mType
The type of this item.
Definition: item_p.h:250
MessageList::Core::ModelInvariantIndex::isValid
bool isValid() const
Returns true if this ModelInvariantIndex is valid, that is, it has been attacched to a ModelInvariant...
Definition: modelinvariantindex.cpp:42
MessageList::Core::ModelPrivate::mLastSelectedMessageInFolder
MessageItem * mLastSelectedMessageInFolder
The id of the preselected ;essage is "translated" to a message pointer when it's fetched from the sto...
Definition: model_p.h:396
MessageList::Core::ModelPrivate::checkIfDateChanged
void checkIfDateChanged()
This is called by MessageList::Manager once in a while.
Definition: model.cpp:871
MessageList::Core::Aggregation::TopmostMessage
The thread grouping is computed from the topmost message (very similar to least recent, but might be different if timezones or machine clocks are screwed)
Definition: aggregation.h:99
MessageList::Core::ModelPrivate::messageDetachedUpdateParentProperties
void messageDetachedUpdateParentProperties(Item *oldParent, MessageItem *mi)
Definition: model.cpp:1906
QList::size
int size() const
MessageList::Core::Model::storageModel
StorageModel * storageModel() const
Returns the StorageModel currently set.
Definition: model.cpp:623
MessageList::Core::StorageModel::prepareForScan
virtual void prepareForScan()=0
Called by Model just before this StorageModel is attacched to it.
MessageList::Core::Model::data
virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Definition: model.cpp:446
MessageList::Core::ModelPrivate::clearThreadingCacheMessageSubjectMD5ToMessageItem
void clearThreadingCacheMessageSubjectMD5ToMessageItem()
Definition: model.cpp:1048
MessageList::Core::Theme::Column
The Column class defines a view column available inside this theme.
Definition: theme.h:565
modelinvariantrowmapper.h
MessageList::Core::ModelPrivate
Definition: model_p.h:33
MessageList::Core::SortOrder::SortDirection
SortDirection
The "generic" sort direction: used for groups and for messages If you add values here please look at ...
Definition: sortorder.h:67
MessageList::Core::Item::setInitialExpandStatus
void setInitialExpandStatus(InitialExpandStatus initialExpandStatus)
Set the initial expand status we have to honor when attacching to the viewable root.
Definition: item.cpp:307
MessageList::Core::ModelPrivate::mView
View * mView
The view we're attacched to.
Definition: model_p.h:273
MessageList::Core::ItemSizeComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:260
MessageList::Core::ItemActionItemStatusComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:399
MessageList::Core::ModelPrivate::mCachedYesterdayLabel
QString mCachedYesterdayLabel
The label for the "Yesterday" group item, cached, so we don't translate it multiple times...
Definition: model_p.h:313
MessageList::Core::StorageModel::preSelectedMessage
unsigned long preSelectedMessage() const
Returns the unique id of the last selected message for this StorageModel.
Definition: storagemodelbase.cpp:41
MessageList::Core::SortOrder::SortMessagesByReceiver
Sort the messages by receiver.
Definition: sortorder.h:85
MessageList::Core::ModelPrivate::mTheme
const Theme * mTheme
The currently used theme: shallow pointer.
Definition: model_p.h:188
MessageList::Core::ModelPrivate::mViewItemJobStepIdleInterval
int mViewItemJobStepIdleInterval
The idle time between two ViewItemJob steps.
Definition: model_p.h:289
QAbstractItemModel::reset
void reset()
QRect
QModelIndex::isValid
bool isValid() const
MessageList::Core::MessageItem::uniqueId
unsigned long uniqueId() const
Definition: messageitem.cpp:534
MessageList::Core::SortOrder::NoGroupSorting
Don't sort the groups at all, add them as they come in.
Definition: sortorder.h:53
MessageList::Core::Item::appendChildItem
int appendChildItem(Model *model, Item *child)
Appends an Item to this item's child list.
Definition: item.cpp:530
MessageList::Core::Item::type
Type type() const
Returns the type of this item.
Definition: item.cpp:297
MessageList::Core::ModelPrivate::mUnassignedMessageListForPass3
QList< MessageItem * > mUnassignedMessageListForPass3
List of unassigned messages, used to handle threading in two passes, pointers are owned! ...
Definition: model_p.h:241
QTime::elapsed
int elapsed() const
QList::count
int count(const T &value) const
MessageList::Core::ModelPrivate::mGroupHeaderItemHash
QHash< QString, GroupHeaderItem * > mGroupHeaderItemHash
Group Key (usually the label) -> GroupHeaderItem, used to quickly find groups, pointers are shallow c...
Definition: model_p.h:208
MessageList::Core::MessageItem::NonThreadable
this message does not look as being threadable
Definition: messageitem.h:77
QList::append
void append(const T &value)
MessageList::Core::View::modelFinishedLoading
void modelFinishedLoading()
This is called by Model to signal that the initial loading stage of a newly attached StorageModel is ...
Definition: view.cpp:1661
MessageList::Core::Aggregation::AlwaysExpandThreads
Expand all threads (this might be very slow)
Definition: aggregation.h:114
MessageList::Core::MessageItem::akonadiItem
Akonadi::Item akonadiItem() const
Definition: messageitem.cpp:540
MessageList::Core::View::ensureDisplayedWithParentsExpanded
void ensureDisplayedWithParentsExpanded(Item *it)
Makes sure that the specified is currently viewable by the user.
Definition: view.cpp:1843
messageitem.h
QHash::constEnd
const_iterator constEnd() const
QTimer
MessageList::Core::Item::firstChildItem
Item * firstChildItem() const
Returns the first child item, if any.
Definition: item.cpp:86
QAbstractItemModel::dataChanged
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
MessageList::Core::PreSelectNewestCentered
Definition: enums.h:49
MessageList::Core::Aggregation::ExpandThreadsWithUnreadMessages
Expand threads with unread messages (this includes new)
Definition: aggregation.h:113
view.h
MessageList::Core::ModelPrivate::mTodayDate
QDate mTodayDate
The today's date.
Definition: model_p.h:262
QHash
MessageList::Core::MessageItem::strippedSubjectMD5
QByteArray strippedSubjectMD5() const
Definition: messageitem.cpp:498
MessageList::Core::Model::mimeData
virtual QMimeData * mimeData(const QModelIndexList &indexes) const
Called when user initiates a drag from the messagelist.
Definition: model.cpp:4527
MessageList::Core::SortOrder::messageSorting
MessageSorting messageSorting() const
Returns the current message sorting option.
Definition: sortorder.h:127
MessageList::Core::ItemUnreadStatusComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:420
MessageList::Core::Aggregation::GroupByReceiver
Group by receiver, always.
Definition: aggregation.h:59
MessageList::Core::View::modelHasBeenReset
void modelHasBeenReset()
This is called by the model from inside setFolder().
Definition: view.cpp:292
MessageList::Core::SortOrder::messageSortDirection
SortDirection messageSortDirection() const
Returns the current message SortDirection.
Definition: sortorder.h:139
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass1Update
ViewItemJobResult viewItemJobStepInternalForJobPass1Update(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:3202
MessageList::Core::Item::useReceiver
bool useReceiver() const
Returns whether sender or receiver is supposed to be displayed.
Definition: item.cpp:466
QList::isEmpty
bool isEmpty() const
MessageList::Core::ItemSenderOrReceiverComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:379
MessageList::Core::MessageItem::messageIdMD5
QByteArray messageIdMD5() const
Definition: messageitem.cpp:450
MessageList::Core::ModelPrivate::mOrphanChildrenHash
QHash< MessageItem *, MessageItem * > mOrphanChildrenHash
Hash of orphan children used in Pass1Cleanup.
Definition: model_p.h:251
QItemSelectionModel::select
virtual void select(const QModelIndex &index, QFlags< QItemSelectionModel::SelectionFlag > command)
QString::isEmpty
bool isEmpty() const
MessageList::Core::ModelPrivate::mPersistentSetManager
MessageItemSetManager * mPersistentSetManager
The "persistent message item sets" are (guess what?) sets of messages that can be referenced globally...
Definition: model_p.h:410
MessageList::Core::Item::ExpandExecuted
Item already expanded.
Definition: item.h:77
MessageList::Core::ItemDateComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:278
MessageList::Core::ModelPrivate::mThreadingCacheMessageSubjectMD5ToMessageItem
QHash< QByteArray, QList< MessageItem * > * > mThreadingCacheMessageSubjectMD5ToMessageItem
Threading cache.
Definition: model_p.h:226
MessageList::Core::ModelPrivate::mLoading
bool mLoading
Set to true in the first large loading job.
Definition: model_p.h:370
QModelIndex::row
int row() const
MessageList::Core::PreSelectOldestCentered
Definition: enums.h:50
MessageList::Core::ModelPrivate::DateChanged
Definition: model_p.h:115
MessageList::Core::Aggregation::AlwaysExpandGroups
All groups are expanded as they are inserted.
Definition: aggregation.h:74
MessageList::Core::ModelPrivate::mPreSelectionMode
PreSelectionMode mPreSelectionMode
Pre-selection is the action of automatically selecting a message just after the folder has finished l...
Definition: model_p.h:382
MessageList::Core::ModelInvariantRowMapper::modelRowsInserted
void modelRowsInserted(int modelIndexRowPosition, int count)
Call this function when rows are inserted to the underlying model BEFORE scanning the model for the n...
Definition: modelinvariantrowmapper.cpp:393
QApplication::setOverrideCursor
void setOverrideCursor(const QCursor &cursor)
MessageList::Core::Item::hasAncestor
bool hasAncestor(const Item *it) const
Return true if Item pointed by it is an ancestor of this item (that is, if it is its parent...
Definition: item.cpp:317
QItemSelectionModel::hasSelection
bool hasSelection() const
MessageList::Core::MessageItem::PerfectParentFound
this message found a perfect parent to attach to
Definition: messageitem.h:74
MessageList::Core::View::updateGeometries
virtual void updateGeometries()
Reimplemented in order to disable update of the geometries while a job step is running (as it takes a...
Definition: view.cpp:218
MessageList::Core::PreSelectionMode
PreSelectionMode
Pre-selection is the action of automatically selecting a message just after the folder has finished l...
Definition: enums.h:44
MessageList::Core::View::setCurrentMessageItem
void setCurrentMessageItem(MessageItem *it, bool center=false)
Sets the current message item.
Definition: view.cpp:882
QApplication::restoreOverrideCursor
void restoreOverrideCursor()
QModelIndex::internalPointer
void * internalPointer() const
MessageList::Core::ModelPrivate::MaxDateChanged
Definition: model_p.h:116
MessageList::Core::Item::dump
void dump(const QString &prefix)
Debug helper.
Definition: item.cpp:552
MessageList::Core::Model::Item
friend class Item
Definition: model.h:79
MessageList::Core::ModelPrivate::attachGroup
void attachGroup(GroupHeaderItem *ghi)
Definition: model.cpp:1087
messageItemNeedsReSorting
static bool messageItemNeedsReSorting(SortOrder::SortDirection messageSortDirection, ItemPrivate *parent, MessageItem *messageItem)
Definition: model.cpp:1704
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass1Fill
ViewItemJobResult viewItemJobStepInternalForJobPass1Fill(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2671
QDate
MessageList::Core::ModelPrivate::slotStorageModelRowsInserted
void slotStorageModelRowsInserted(const QModelIndex &parent, int from, int to)
Definition: model.cpp:4122
MessageList::Core::View::slotSelectionChanged
void slotSelectionChanged(const QItemSelection &current, const QItemSelection &)
Handles selection item management.
Definition: view.cpp:1920
MessageList::Core::Item::killAllChildItems
void killAllChildItems()
Kills all the child items without emitting any signal, recursively.
Definition: item.cpp:379
MessageList::Core::Item::childItem
Item * childItem(int idx) const
Returns the child item at position idx or 0 if idx is out of the allowable range. ...
Definition: item.cpp:75
MessageList::Core::ModelPrivate::slotStorageModelHeaderDataChanged
void slotStorageModelHeaderDataChanged(Qt::Orientation orientation, int first, int last)
Definition: model.cpp:4491
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass1Cleanup
ViewItemJobResult viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2951
MessageList::Core::ModelPrivate::mViewItemJobStepStartTime
time_t mViewItemJobStepStartTime
The time at the current ViewItemJob step started.
Definition: model_p.h:279
QList::first
T & first()
MessageList::Core::ModelPrivate::slotStorageModelRowsRemoved
void slotStorageModelRowsRemoved(const QModelIndex &parent, int from, int to)
Definition: model.cpp:4254
MessageList::Core::ModelPrivate::ViewItemJobInterrupted
Definition: model_p.h:72
MessageList::Core::Model::setSortOrder
void setSortOrder(const SortOrder *sortOrder)
Sets the sort order.
Definition: model.cpp:341
MessageList::Core::Item::setParent
void setParent(Item *pParent)
Sets the parent for this item.
Definition: item.cpp:396
QItemSelectionModel::clearSelection
void clearSelection()
QString
QList
MessageList::Core::Item::initialExpandStatus
InitialExpandStatus initialExpandStatus() const
The initial expand status we have to honor when attacching to the viewable root.
Definition: item.cpp:302
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass2
ViewItemJobResult viewItemJobStepInternalForJobPass2(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2561
manager.h
QAbstractItemModel::rowsRemoved
void rowsRemoved(const QModelIndex &parent, int start, int end)
QAbstractScrollArea::verticalScrollBar
QScrollBar * verticalScrollBar() const
MessageList::Core::ModelPrivate::q
Model *const q
Definition: model_p.h:170
MessageList::Core::Item::GroupHeader
This item is a GroupHeaderItem.
Definition: item.h:63
QTreeView::isExpanded
bool isExpanded(const QModelIndex &index) const
MessageList::Core::ModelInvariantRowMapper::createModelInvariantIndex
void createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
Binds a ModelInvariantIndex structure to the specified CURRENT modelIndexRow.
Definition: modelinvariantrowmapper.cpp:344
MessageList::Core::ModelPrivate::applyFilterToSubtree
bool applyFilterToSubtree(Item *item, const QModelIndex &parentIndex)
Recursively applies the current filter to the tree originating at the specified item.
Definition: model.cpp:377
MessageList::Core::StorageModel::PerfectThreadingPlusReferences
messageIdMD5, inReplyToMD5, referencesIdMD5
Definition: storagemodelbase.h:98
MessageList::Core::StorageModel::setMessageItemStatus
virtual void setMessageItemStatus(MessageItem *mi, int row, const Akonadi::MessageStatus &status)=0
This method should use the inner model implementation to associate the new status to the specified me...
groupheaderitem.h
MessageList::Core::ModelPrivate::slotStorageModelDataChanged
void slotStorageModelDataChanged(const QModelIndex &fromIndex, const QModelIndex &toIndex)
Definition: model.cpp:4437
MessageList::Core::Aggregation::FavorSpeed
Do larger chunks of work, zero intervals between chunks.
Definition: aggregation.h:127
MessageList::Core::Model::deletePersistentSet
void deletePersistentSet(MessageItemSetReference ref)
Deletes the persistent set pointed by the specified reference.
Definition: model.cpp:4585
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass4
ViewItemJobResult viewItemJobStepInternalForJobPass4(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2419
QAbstractItemModel::createIndex
QModelIndex createIndex(int row, int column, void *ptr) const
INSERT_MESSAGE_WITH_COMPARATOR
#define INSERT_MESSAGE_WITH_COMPARATOR(_ItemComparator)
MessageList::Core::Item::Message
This item is a MessageItem.
Definition: item.h:64
MessageList::Core::SortOrder::SortMessagesBySender
Sort the messages by sender.
Definition: sortorder.h:84
MessageList::Core::Model::Model
Model(View *pParent)
Creates the mighty Model attached to the specified View.
Definition: model.cpp:265
QList::end
iterator end()
MessageList::Core::Item::InvisibleRoot
This item is just Item and it's the only InvisibleRoot per Model.
Definition: item.h:65
MessageList::Core::ModelPrivate::mUnassignedMessageListForPass2
QList< MessageItem * > mUnassignedMessageListForPass2
List of unassigned messages, used to handle threading in two passes, pointers are owned! ...
Definition: model_p.h:236
MessageList::Core::View::modelAboutToEmitLayoutChanged
void modelAboutToEmitLayoutChanged()
Definition: view.cpp:695
MessageList::Core::ModelPrivate::mViewItemJobStepMessageCheckCount
int mViewItemJobStepMessageCheckCount
The number of messages we process at once in a ViewItemJob step without checking the timeouts above...
Definition: model_p.h:295
MessageList::Core::Aggregation::groupExpandPolicy
GroupExpandPolicy groupExpandPolicy() const
Returns the current GroupExpandPolicy.
Definition: aggregation.h:181
MessageList::Core::Model::setFilter
void setFilter(const Filter *filter)
Sets the Filter to be applied on messages.
Definition: model.cpp:351
MessageList::Core::ModelPrivate::mFillStepTimer
QTimer mFillStepTimer
The timer involved in breaking the "fill" operation in steps.
Definition: model_p.h:203
QAbstractSlider::setValue
void setValue(int)
MessageList::Core::Item
A single item of the MessageList tree managed by MessageList::Model.
Definition: item.h:52
MessageList::Core::Model::createPersistentSet
MessageItemSetReference createPersistentSet(const QList< MessageItem * > &items)
Creates a persistent set for the specified MessageItems and returns its reference.
Definition: model.cpp:4564
QList::contains
bool contains(const T &value) const
QTimer::stop
void stop()
QVariant::fromValue
QVariant fromValue(const T &value)
MessageList::Core::ItemSubjectComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:319
MessageList::Core::ItemImportantStatusComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:444
MessageList::Core::ModelPrivate::saveExpandedStateOfSubtree
void saveExpandedStateOfSubtree(Item *root)
Save the expanded state of the subtree with the specified root.
Definition: model.cpp:1162
MessageList::Core::Aggregation::ExpandThreadsWithUnreadOrImportantMessages
Expand threads with "hot" messages (this includes new, unread, important, todo)
Definition: aggregation.h:115
MessageList::Core::Item::childItems
QList< Item * > * childItems() const
Return the list of child items.
Definition: item.cpp:70
MessageList::Core::ModelPrivate::slotApplyFilter
void slotApplyFilter()
Definition: model.cpp:361
MessageList::Core::MessageTypeUnreadOnly
Definition: enums.h:59
MessageList::Core::Model::persistentSetCurrentMessageItemList
QList< MessageItem * > persistentSetCurrentMessageItemList(MessageItemSetReference ref)
Returns the list of MessageItems that are still existing in the set pointed by the specified referenc...
Definition: model.cpp:4577
MessageList::Core::SortOrder::SortGroupsBySender
Sort groups by sender (makes sense only with GroupBySender)
Definition: sortorder.h:57
MessageList::Core::ModelPrivate::AttachmentStatusChanged
Definition: model_p.h:120
MessageList::Core::MessageItem::threadingStatus
ThreadingStatus threadingStatus() const
Definition: messageitem.cpp:522
MessageList::Core::ModelPrivate::clear
void clear()
Definition: model.cpp:628
MessageList::Core::ItemReceiverComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:359
MessageList::Core::Model::setTheme
void setTheme(const Theme *theme)
Sets the Theme.
Definition: model.cpp:336
MessageList::Core::Aggregation::NeverExpandThreads
Never expand any thread, this is fast.
Definition: aggregation.h:111
QTime::currentTime
QTime currentTime()
MessageList::Core::StorageModel
The QAbstractItemModel based interface that you need to provide for your storage to work with Message...
Definition: storagemodelbase.h:45
MessageList::Core::View::modelEmittedLayoutChanged
void modelEmittedLayoutChanged()
Definition: view.cpp:701
QItemSelection
MessageList::Core::SortOrder::NoMessageSorting
Don't sort the messages at all.
Definition: sortorder.h:80
MessageList::Core::ModelPrivate::viewItemJobStep
void viewItemJobStep()
Definition: model.cpp:3891
QHash::constBegin
const_iterator constBegin() const
MessageList::Core::ModelInvariantRowMapper::modelIndexRowRangeToModelInvariantIndexList
QList< ModelInvariantIndex * > * modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count)
This basically applies modelIndexRowToModelInvariantIndex() to a range of elements.
Definition: modelinvariantrowmapper.cpp:363
MessageList::Core::ModelPrivate::mInLengthyJobBatch
bool mInLengthyJobBatch
Flag signaling a possibly long job batch.
Definition: model_p.h:354
MessageList::Core::ModelPrivate::mRecursionCounterForReset
int mRecursionCounterForReset
counter to avoid infinite recursions in the setStorageModel() function
Definition: model_p.h:173
MessageList::Core::StorageModel::initializeMessageItem
virtual bool initializeMessageItem(MessageItem *it, int row, bool bUseReceiver) const =0
This method should use the inner model implementation to fill in the base data for the specified Mess...
MessageList::Core::Aggregation::MostRecentMessage
The thread grouping is computed from the most recent message.
Definition: aggregation.h:100
MessageList::Core::Aggregation::FavorInteractivity
Do small chunks of work, small intervals between chunks to allow for UI event processing.
Definition: aggregation.h:126
MessageList::Core::SortOrder::SortMessagesBySenderOrReceiver
Sort the messages by sender or receiver.
Definition: sortorder.h:83
QDateTime::date
QDate date() const
MessageList::Core::MessageItem::subjectIsPrefixed
bool subjectIsPrefixed() const
Definition: messageitem.cpp:492
MessageList::Core::MessageItem::accessibleText
QString accessibleText(const MessageList::Core::Theme *theme, int columnIndex)
Definition: messageitem.cpp:588
MessageList::Core::ModelPrivate::ViewItemJobResult
ViewItemJobResult
Definition: model_p.h:69
MessageList::Core::PreSelectNone
Definition: enums.h:46
QList::takeFirst
T takeFirst()
MessageList::Core::ModelPrivate::addMessageToSubjectBasedThreadingCache
void addMessageToSubjectBasedThreadingCache(MessageItem *mi)
Definition: model.cpp:1540
QLatin1String
MessageList::Core::Model::columnCount
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const
Definition: model.cpp:437
MessageList::Core::Item::maxDate
time_t maxDate() const
Returns the maximum date in the subtree originating from this item.
Definition: item.cpp:431
MessageList::Core::Filter::match
bool match(const MessageItem *item) const
Returns true if the specified parameters match this filter and false otherwise.
Definition: filter.cpp:48
QList::insert
void insert(int i, const T &value)
MessageList::Core::SortOrder::Ascending
Definition: sortorder.h:69
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJob
ViewItemJobResult viewItemJobStepInternalForJob(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:3350
MessageList::Core::Model::setStorageModel
void setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode=PreSelectLastSelected)
Sets the storage model from that the messages to be displayed should be fetched.
Definition: model.cpp:672
MessageList::Core::Item::d_ptr
ItemPrivate *const d_ptr
Definition: item.h:402
MessageList::Core::Model::sortOrder
const SortOrder * sortOrder() const
Returns the sort order.
Definition: model.cpp:346
MessageList::Core::SortOrder::SortMessagesByImportantStatus
Definition: sortorder.h:90
MessageList::Core::Theme::column
Column * column(int idx) const
Returns a pointer to the column at the specified index or 0 if there is no such column.
Definition: theme.h:947
MessageList::Core::SortOrder::groupSorting
GroupSorting groupSorting() const
Returns the GroupSorting.
Definition: sortorder.h:100
MessageList::Core::ModelPrivate::mSortOrder
const SortOrder * mSortOrder
The currently used sort order.
Definition: model_p.h:193
QDate::currentDate
QDate currentDate()
MessageList::Core::ModelPrivate::mCachedLastWeekLabel
QString mCachedLastWeekLabel
The label for the "Last Week" group item, cached, so we don't translate it multiple times...
Definition: model_p.h:323
QAbstractItemModel::headerDataChanged
void headerDataChanged(Qt::Orientation orientation, int first, int last)
MessageList::Core::StorageModel::PerfectThreadingReferencesAndSubject
All of the above plus subject stuff.
Definition: storagemodelbase.h:99
MessageList::Core::ModelPrivate::findMessageParent
MessageItem * findMessageParent(MessageItem *mi)
Attempt to find the threading parent for the specified message item.
Definition: model.cpp:1376
MessageList::Core::View::setRowHidden
virtual void setRowHidden(int row, const QModelIndex &parent, bool hide)
Called to hide or show the specified row from the view.
Definition: view.cpp:2732
MessageList::Core::Item::sender
const QString & sender() const
Returns the sender associated to this item.
Definition: item.cpp:441
MessageList::Core::Filter
This class is responsable of matching messages that should be displayed in the View.
Definition: filter.h:44
MessageList::Core::Model::headerData
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
Definition: model.cpp:494
MessageList::Core::Aggregation::ExpandThreadsWithNewMessages
DEPRECATED. New message status no longer exists.
Definition: aggregation.h:112
MessageList::Core::Aggregation::fillViewStrategy
FillViewStrategy fillViewStrategy() const
Returns the current fill view strategy.
Definition: aggregation.h:266
MessageList::Core::StorageModel::updateMessageItemData
virtual void updateMessageItemData(MessageItem *mi, int row) const =0
This method should use the inner model implementation to re-fill the date, the status, the encryption state, the signature state and eventually update the min/max dates for the specified MessageItem from the underlying storage slot at the specified row index.
MessageList::Core::Model::index
virtual QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
Definition: model.cpp:545
QModelIndex::column
int column() const
MessageList::Core::Item::setMaxDate
void setMaxDate(time_t date)
Sets the maximum date in the subtree originating from this item.
Definition: item.cpp:436
QByteArray::data
char * data()
MessageList::Core::ModelPrivate::mInvariantRowMapper
ModelInvariantRowMapper * mInvariantRowMapper
Our mighty ModelInvariantRowMapper: used to workaround an issue related to the Model/View architectur...
Definition: model_p.h:303
MessageList::Core::Model::flags
virtual Qt::ItemFlags flags(const QModelIndex &index) const
Definition: model.cpp:4499
MessageList::Core::ModelPrivate::mCachedThreeWeeksAgoLabel
QString mCachedThreeWeeksAgoLabel
The label for the "Three Weeks Ago" group item, cached, so we don't translate it multiple times...
Definition: model_p.h:333
MessageList::Core::ModelPrivate::removeMessageFromSubjectBasedThreadingCache
void removeMessageFromSubjectBasedThreadingCache(MessageItem *mi)
Definition: model.cpp:1570
QAbstractItemModel
MessageList::Core::Theme::Column::label
const QString & label() const
Returns the label set for this column.
Definition: theme.h:675
MessageList::Core::Item::topmostNonRoot
Item * topmostNonRoot()
Returns the topmost parent item that is not a Root item (that is, is a Message or GroupHeader)...
Definition: item.cpp:192
CHECK_IF_GROUP_NEEDS_RESORTING
#define CHECK_IF_GROUP_NEEDS_RESORTING(_ItemDateComparator)
MessageList::Core::SortOrder::SortGroupsBySenderOrReceiver
Sort groups by sender or receiver (makes sense only with GroupBySenderOrReceiver) ...
Definition: sortorder.h:56
QTimer::start
void start(int msec)
MessageList::Core::ModelPrivate::mStorageModel
StorageModel * mStorageModel
The currently set storage model: shallow pointer.
Definition: model_p.h:178
MessageList::Core::SortOrder::SortMessagesByUnreadStatus
Sort the messages by the "Unread" flags of status.
Definition: sortorder.h:89
MessageList::Core::StorageModel::fillMessageItemThreadingData
virtual void fillMessageItemThreadingData(MessageItem *mi, int row, ThreadingDataSubset subset) const =0
This method should use the inner model implementation to fill in the specified subset of threading da...
QTime::start
void start()
MessageList::Core::ModelInvariantRowMapper::modelInvariantIndexToModelIndexRow
int modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant)
Maps a ModelInvariantIndex to the CURRENT associated row index in the model.
Definition: modelinvariantrowmapper.cpp:252
MessageList::Core::SortOrder::SortMessagesBySize
Sort the messages by size.
Definition: sortorder.h:87
MessageList::Core::ModelPrivate::viewItemJobStepInternal
ViewItemJobResult viewItemJobStepInternal()
Definition: model.cpp:3683
MessageList::Core::ModelPrivate::attachMessageToGroupHeader
void attachMessageToGroupHeader(MessageItem *mi)
Definition: model.cpp:1214
MessageList::Core::SortOrder::SortMessagesByDateTime
Sort the messages by date and time.
Definition: sortorder.h:81
MessageList::Core::Item::status
const Akonadi::MessageStatus & status() const
Returns the status associated to this Item.
Definition: item.cpp:401
model_p.h
MessageList::Core::View::modelJobBatchStarted
void modelJobBatchStarted()
This is called by Model to signal a start of a lengthy job batch.
Definition: view.cpp:280
MessageList::Core::Theme
The Theme class defines the visual appearance of the MessageList.
Definition: theme.h:65
MessageList::Core::Model::setAggregation
void setAggregation(const Aggregation *aggregation)
Sets the Aggregation mode.
Definition: model.cpp:329
MessageList::Core::ModelPrivate::UnreadStatusChanged
Definition: model_p.h:118
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass3
ViewItemJobResult viewItemJobStepInternalForJobPass3(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2468
MessageList::Core::Item::takeChildItem
void takeChildItem(Model *model, Item *child)
Removes a child from this item's child list without deleting it.
Definition: item.cpp:568
QList::constEnd
const_iterator constEnd() const
QTreeView::expand
void expand(const QModelIndex &index)
QList::constBegin
const_iterator constBegin() const
MessageList::Core::ModelPrivate::mCachedFiveWeeksAgoLabel
QString mCachedFiveWeeksAgoLabel
The label for the "Five Weeks Ago" group item, cached, so we don't translate it multiple times...
Definition: model_p.h:343
QAbstractItemView::currentIndex
QModelIndex currentIndex() const
MessageList::Core::ModelPrivate::clearJobList
void clearJobList()
Definition: model.cpp:1066
QAbstractItemModel::rowsInserted
void rowsInserted(const QModelIndex &parent, int start, int end)
QTreeView::setRootIsDecorated
void setRootIsDecorated(bool show)
MessageList::Core::ModelPrivate::mNewestItem
MessageItem * mNewestItem
Definition: model_p.h:387
QTimer::isActive
bool isActive() const
QObject::connect
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
MessageList::Core::Aggregation::BatchNoInteractivity
Do one large chunk, no interactivity at all.
Definition: aggregation.h:128
QObject::parent
QObject * parent() const
MessageList::Core::Item::parent
Item * parent() const
Returns the parent Item in the tree, or 0 if this item isn't attached to the tree.
Definition: item.cpp:391
MessageList::Core::Aggregation::GroupBySenderOrReceiver
Group by sender (incoming) or receiver (outgoing) field.
Definition: aggregation.h:57
MessageList::Core::MessageItemSetManager::setCount
int setCount() const
Definition: messageitemsetmanager.cpp:46
MessageList::Core::ModelPrivate::clearUnassignedMessageLists
void clearUnassignedMessageLists()
Definition: model.cpp:919
MessageList::Core::Aggregation::threadExpandPolicy
ThreadExpandPolicy threadExpandPolicy() const
Returns the current thread expand policy.
Definition: aggregation.h:244
MessageList::Core::PreSelectLastSelected
Definition: enums.h:47
MessageList::Core::ModelPrivate::mCurrentItemToRestoreAfterViewItemJobStep
Item * mCurrentItemToRestoreAfterViewItemJobStep
We need to save the current item before each job step.
Definition: model_p.h:362
MessageList::Core::ModelInvariantIndex
An invariant index that can be ALWAYS used to reference an item inside a QAbstractItemModel.
Definition: modelinvariantindex.h:44
storagemodelbase.h
MessageList::Core::View::selectFirstMessageItem
bool selectFirstMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem)
Selects the first message item in the view that matches messageTypeFilter.
Definition: view.cpp:1609
MessageList::Core::ModelPrivate::mAggregation
const Aggregation * mAggregation
The currently set aggregation mode: shallow pointer set by Widget.
Definition: model_p.h:183
MessageList::Core::SortOrder::SortMessagesByActionItemStatus
Sort the messages by the "Action Item" flag of status.
Definition: sortorder.h:88
INSERT_GROUP_WITH_COMPARATOR
#define INSERT_GROUP_WITH_COMPARATOR(_ItemComparator)
QList::begin
iterator begin()
item_p.h
MessageList::Core::ModelPrivate::ActionItemStatusChanged
Definition: model_p.h:117
MessageList::Core::MessageItemSetReference
long int MessageItemSetReference
Definition: messageitemsetmanager.h:33
MessageList::Core::MessageItem::referencesIdMD5
QByteArray referencesIdMD5() const
Definition: messageitem.cpp:474
MessageList::Core::Item::initialSetup
void initialSetup(time_t date, size_t size, const QString &sender, const QString &receiver, bool useReceiver)
This is meant to be called right after the constructor.
Definition: item.cpp:481
MessageList::Core::SortOrder::SortGroupsByDateTimeOfMostRecent
Sort groups by date/time of the most recent message.
Definition: sortorder.h:55
MessageList::Core::SortOrder::SortMessagesByAttachmentStatus
Sort the messages By "Important" flags of status.
Definition: sortorder.h:91
MessageList::Core::MessageItem::inReplyToIdMD5
QByteArray inReplyToIdMD5() const
Definition: messageitem.cpp:462
messageitemsetmanager.h
MessageList::Core::Model::isLoading
bool isLoading() const
Returns true if the view is currently loading, that is it's in the first (possibly lenghty) job batch...
Definition: model.cpp:4547
QDateTime
MessageList::Core::View::ignoreUpdateGeometries
void ignoreUpdateGeometries(bool ignore)
Used to enable/disable the ignoring of updateGeometries() calls.
Definition: view.cpp:176
MessageList::Core::StorageModel::PerfectThreadingOnly
Only the data for messageIdMD5 and inReplyToMD5 is needed.
Definition: storagemodelbase.h:97
QDateTime::setTime_t
void setTime_t(uint seconds)
MessageList::Core::Item::indexOfChildItem
int indexOfChildItem(Item *item) const
Returns the actual index of the child Item item or -1 if item is not a child of this Item...
Definition: item.cpp:173
MessageList::Core::StorageModel::containsOutboundMessages
virtual bool containsOutboundMessages() const =0
Returns true if this StorageModel (folder) contains outbound messages and false otherwise.
MessageList::Core::ModelPrivate::mCachedTodayLabel
QString mCachedTodayLabel
The label for the "Today" group item, cached, so we don't translate it multiple times.
Definition: model_p.h:308
MessageList::Core::Theme::columns
const QList< Column * > & columns() const
Returns the list of columns available in this theme.
Definition: theme.h:941
QTimer::setSingleShot
void setSingleShot(bool singleShot)
QVariant
MessageList::Core::ModelInvariantRowMapper::modelRowsRemoved
QList< ModelInvariantIndex * > * modelRowsRemoved(int modelIndexRowPosition, int count)
Call this function when rows are removed from the underlying model AFTER accessing the removed rows f...
Definition: modelinvariantrowmapper.cpp:459
Qt::ItemFlags
typedef ItemFlags
QString::toUtf8
QByteArray toUtf8() const
MessageList::Core::Item::setStatus
void setStatus(const Akonadi::MessageStatus &status)
Sets the status associated to this Item.
Definition: item.cpp:406
MessageList::Core::ModelPrivate::propagateItemPropertiesToParent
void propagateItemPropertiesToParent(Item *item)
This one checks if the parent of item requires an update due to the properties of item (that might ha...
Definition: model.cpp:1956
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:32:01 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

messagelist

Skip menu "messagelist"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members

kdepim API Reference

Skip menu "kdepim API Reference"
  • akonadi_next
  • akregator
  • blogilo
  • calendarsupport
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt2
  • kjots
  • kleopatra
  • kmail
  • knode
  • knotes
  • kontact
  • korgac
  • korganizer
  • ktimetracker
  • libkdepim
  • libkleo
  • libkpgp
  • mailcommon
  • messagelist
  • messageviewer
  • pimprint

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