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

messagelist

  • sources
  • kde-4.12
  • 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 <KLocale>
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  default:
1803  // this kind of message sorting isn't affected by the property changes: nothing to do.
1804  break;
1805  }
1806  }
1807 
1808  return false; // the invisible root isn't affected by any change.
1809  }
1810 
1811  if ( parent->type() == Item::GroupHeader )
1812  {
1813  // item is a message attacched to a GroupHeader.
1814  // It might need re-grouping or re-sorting (within the same group)
1815 
1816  // Check re-grouping here.
1817  if (
1818  (
1819  // max date changed
1820  ( propertyChangeMask & MaxDateChanged ) &&
1821  // thread leader is most recent message
1822  ( mAggregation->threadLeader() == Aggregation::MostRecentMessage )
1823  ) || (
1824  // date changed
1825  ( propertyChangeMask & DateChanged ) &&
1826  // thread leader the topmost message
1827  ( mAggregation->threadLeader() == Aggregation::TopmostMessage )
1828  )
1829  )
1830  {
1831  // Might really need re-grouping.
1832  // attachMessageToGroupHeader() will find the right group for this message
1833  // and if it's different than the current it will move it.
1834  attachMessageToGroupHeader( static_cast< MessageItem * >( item ) );
1835  // Re-grouping fixes the properties of the involved group headers
1836  // so at exit of attachMessageToGroupHeader() the parent can't be affected
1837  // by the change anymore.
1838  return false;
1839  }
1840 
1841  // Re-grouping wasn't needed. Re-sorting might be.
1842 
1843  } // else item is a message attacched to another message and might need re-sorting only.
1844 
1845  // Check if message needs re-sorting.
1846 
1847  switch ( mSortOrder->messageSorting() )
1848  {
1849  case SortOrder::SortMessagesByDateTime:
1850  if ( propertyChangeMask & DateChanged ) // date changed
1851  {
1852  if ( messageItemNeedsReSorting< ItemDateComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1853  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1854  } // else date changed, but it doesn't match sorting order: no need to re-sort
1855  break;
1856  case SortOrder::SortMessagesByDateTimeOfMostRecent:
1857  if ( propertyChangeMask & MaxDateChanged ) // max date changed
1858  {
1859  if ( messageItemNeedsReSorting< ItemMaxDateComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1860  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1861  } // else max date changed, but it doesn't match sorting order: no need to re-sort
1862  break;
1863  case SortOrder::SortMessagesByActionItemStatus:
1864  if ( propertyChangeMask & ActionItemStatusChanged ) // todo status changed
1865  {
1866  if ( messageItemNeedsReSorting< ItemActionItemStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1867  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1868  } // else to do status changed, but it doesn't match sorting order: no need to re-sort
1869  break;
1870  case SortOrder::SortMessagesByUnreadStatus:
1871  if ( propertyChangeMask & UnreadStatusChanged ) // new / unread status changed
1872  {
1873  if ( messageItemNeedsReSorting< ItemUnreadStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1874  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1875  } // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
1876  break;
1877  case SortOrder::SortMessagesByImportantStatus:
1878  if ( propertyChangeMask & ImportantStatusChanged ) // important status changed
1879  {
1880  if ( messageItemNeedsReSorting< ItemImportantStatusComparator >( mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >( item ) ) )
1881  attachMessageToParent( parent, static_cast< MessageItem * >( item ) );
1882  } // else important status changed, but it doesn't match sorting order: no need to re-sort
1883  break;
1884  default:
1885  // this kind of message sorting isn't affected by property changes: nothing to do.
1886  break;
1887  }
1888 
1889  return true; // parent might be affected too.
1890 }
1891 
1892 void ModelPrivate::messageDetachedUpdateParentProperties( Item *oldParent, MessageItem *mi )
1893 {
1894  Q_ASSERT( oldParent );
1895  Q_ASSERT( mi );
1896  Q_ASSERT( oldParent != mRootItem );
1897 
1898 
1899  // oldParent might have its properties changed because of the child removal.
1900  // propagate the changes up.
1901  for(;;)
1902  {
1903  // pParent is not the root item now. This is assured by how we enter this loop
1904  // and by the fact that handleItemPropertyChanges returns false when grandParent
1905  // is Item::InvisibleRoot. We could actually assert it here...
1906 
1907  // Check if its dates need an update.
1908  int propertyChangeMask;
1909 
1910  if ( ( mi->maxDate() == oldParent->maxDate() ) && oldParent->recomputeMaxDate() )
1911  propertyChangeMask = MaxDateChanged;
1912  else
1913  break; // from the for(;;) loop
1914 
1915  // One of the oldParent properties has changed for sure
1916 
1917  Item * grandParent = oldParent->parent();
1918 
1919  // If there is no grandParent then oldParent isn't attacched to the view.
1920  // Re-sorting / re-grouping isn't needed for sure.
1921  if ( !grandParent )
1922  break; // from the for(;;) loop
1923 
1924  // The following function will return true if grandParent may be affected by the change.
1925  // If the grandParent isn't affected, we stop climbing.
1926  if ( !handleItemPropertyChanges( propertyChangeMask, grandParent, oldParent ) )
1927  break; // from the for(;;) loop
1928 
1929  // Now we need to climb up one level and check again.
1930  oldParent = grandParent;
1931  } // for(;;) loop
1932 
1933  // If the last message was removed from a group header then this group will need an update
1934  // for sure. We will need to remove it (unless a message is attacched back to it)
1935  if ( oldParent->type() == Item::GroupHeader )
1936  {
1937  if ( oldParent->childItemCount() == 0 )
1938  mGroupHeadersThatNeedUpdate.insert( static_cast< GroupHeaderItem * >( oldParent ), static_cast< GroupHeaderItem * >( oldParent ) );
1939  }
1940 }
1941 
1942 void ModelPrivate::propagateItemPropertiesToParent( Item * item )
1943 {
1944  Item * pParent = item->parent();
1945  Q_ASSERT( pParent );
1946  Q_ASSERT( pParent != mRootItem );
1947 
1948  for(;;)
1949  {
1950  // pParent is not the root item now. This is assured by how we enter this loop
1951  // and by the fact that handleItemPropertyChanges returns false when grandParent
1952  // is Item::InvisibleRoot. We could actually assert it here...
1953 
1954  // Check if its dates need an update.
1955  int propertyChangeMask;
1956 
1957  if ( item->maxDate() > pParent->maxDate() )
1958  {
1959  pParent->setMaxDate( item->maxDate() );
1960  propertyChangeMask = MaxDateChanged;
1961  } else {
1962  // No parent dates have changed: no further work is needed. Stop climbing here.
1963  break; // from the for(;;) loop
1964  }
1965 
1966  // One of the pParent properties has changed.
1967 
1968  Item * grandParent = pParent->parent();
1969 
1970  // If there is no grandParent then pParent isn't attacched to the view.
1971  // Re-sorting / re-grouping isn't needed for sure.
1972  if ( !grandParent )
1973  break; // from the for(;;) loop
1974 
1975  // The following function will return true if grandParent may be affected by the change.
1976  // If the grandParent isn't affected, we stop climbing.
1977  if ( !handleItemPropertyChanges( propertyChangeMask, grandParent, pParent ) )
1978  break; // from the for(;;) loop
1979 
1980  // Now we need to climb up one level and check again.
1981  pParent = grandParent;
1982 
1983  } // for(;;)
1984 }
1985 
1986 
1987 void ModelPrivate::attachMessageToParent( Item *pParent, MessageItem *mi )
1988 {
1989  Q_ASSERT( pParent );
1990  Q_ASSERT( mi );
1991 
1992  // This function may be called to do a simple "re-sort" of the item inside the parent.
1993  // In that case mi->parent() is equal to pParent.
1994  bool oldParentWasTheSame;
1995 
1996  if ( mi->parent() )
1997  {
1998  Item * oldParent = mi->parent();
1999 
2000  // The item already had a parent and this means that we're moving it.
2001  oldParentWasTheSame = oldParent == pParent; // just re-sorting ?
2002 
2003  if ( mi->isViewable() ) // is actually
2004  {
2005  // The message is actually attached to the viewable root
2006 
2007  // Unfortunately we need to hack the model/view architecture
2008  // since it's somewhat flawed in this. At the moment of writing
2009  // there is simply no way to atomically move a subtree.
2010  // We must detach, call beginRemoveRows()/endRemoveRows(),
2011  // save the expanded state, save the selection, save the current item,
2012  // save the view position (YES! As we are removing items the view
2013  // will hopelessly jump around so we're just FORCED to break
2014  // the isolation from the view)...
2015  // ...*then* reattach, restore the expanded state, restore the selection,
2016  // restore the current item, restore the view position and pray
2017  // that nothing will fail in the (rather complicated) process....
2018 
2019  // Yet more unfortunately, while saving the expanded state might stop
2020  // at a certain (unexpanded) point in the tree, saving the selection
2021  // is hopelessly recursive down to the bare leafs.
2022 
2023  // Furthermore the expansion of items is a common case while selection
2024  // in the subtree is rare, so saving it would be a huge cost with
2025  // a low revenue.
2026 
2027  // This is why we just let the selection screw up. I hereby refuse to call
2028  // yet another expensive recursive function here :D
2029 
2030  // The current item saving can be somewhat optimized doing it once for
2031  // a single job step...
2032 
2033  if (
2034  ( ( mi )->childItemCount() > 0 ) && // has children
2035  mModelForItemFunctions && // the UI is not actually disconnected
2036  mView->isExpanded( q->index( mi, 0 ) ) // is actually expanded
2037  )
2038  saveExpandedStateOfSubtree( mi );
2039  }
2040 
2041  // If the parent is viewable (so mi was viewable too) then the beginRemoveRows()
2042  // and endRemoveRows() functions of this model will be called too.
2043  oldParent->takeChildItem( mModelForItemFunctions, mi );
2044 
2045  if ( ( !oldParentWasTheSame ) && ( oldParent != mRootItem ) )
2046  messageDetachedUpdateParentProperties( oldParent, mi );
2047 
2048  } else {
2049  // The item had no parent yet.
2050  oldParentWasTheSame = false;
2051  }
2052 
2053  // Take care of perfect / imperfect threading.
2054  // Items that are now perfectly threaded, but already have a different parent
2055  // might have been imperfectly threaded before. Remove them from the caches.
2056  // Items that are now imperfectly threaded must be added to the caches.
2057  //
2058  // If we're just re-sorting the item inside the same parent then the threading
2059  // caches don't need to be updated (since they actually depend on the parent).
2060 
2061  if ( !oldParentWasTheSame )
2062  {
2063  switch( mi->threadingStatus() )
2064  {
2065  case MessageItem::PerfectParentFound:
2066  if ( !mi->inReplyToIdMD5().isEmpty() )
2067  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove( mi->inReplyToIdMD5(), mi );
2068  break;
2069  case MessageItem::ImperfectParentFound:
2070  case MessageItem::ParentMissing: // may be: temporary or just fallback assignment
2071  if ( !mi->inReplyToIdMD5().isEmpty() )
2072  {
2073  if ( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( mi->inReplyToIdMD5(), mi ) )
2074  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert( mi->inReplyToIdMD5(), mi );
2075  }
2076  break;
2077  case MessageItem::NonThreadable: // this also happens when we do no threading at all
2078  // make gcc happy
2079  Q_ASSERT( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( mi->inReplyToIdMD5(), mi ) );
2080  break;
2081  }
2082  }
2083 
2084  // Set the new parent
2085  mi->setParent( pParent );
2086 
2087  // Propagate watched and ignored status
2088  if (
2089  ( pParent->status().toQInt32() & mCachedWatchedOrIgnoredStatusBits ) && // unlikely
2090  ( pParent->type() == Item::Message ) // likely
2091  )
2092  {
2093  // the parent is either watched or ignored: propagate to the child
2094  if ( pParent->status().isWatched() )
2095  {
2096  int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow( mi );
2097  mi->setStatus( Akonadi::MessageStatus::statusWatched() );
2098  mStorageModel->setMessageItemStatus( mi, row, Akonadi::MessageStatus::statusWatched() );
2099  } else if ( pParent->status().isIgnored() )
2100  {
2101  int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow( mi );
2102  mi->setStatus( Akonadi::MessageStatus::statusIgnored() );
2103  mStorageModel->setMessageItemStatus( mi, row, Akonadi::MessageStatus::statusIgnored() );
2104  }
2105  }
2106 
2107  // And insert into its child list
2108 
2109  // If pParent is viewable then the insert/append functions will call this model's
2110  // beginInsertRows() and endInsertRows() functions. This is EXTREMELY
2111  // expensive and ugly but it's the only way with the Qt4 imposed Model/View method.
2112  // Dude... (citation from Lost, if it wasn't clear).
2113 
2114  // I'm using a macro since it does really improve readability.
2115  // I'm NOT using a helper function since gcc will refuse to inline some of
2116  // the calls because they make this function grow too much.
2117 #define INSERT_MESSAGE_WITH_COMPARATOR( _ItemComparator ) \
2118  if ( ( mSortOrder->messageSortDirection() == SortOrder::Ascending ) \
2119  || ( pParent->type() == Item::Message ) ) \
2120  { \
2121  pParent->d_ptr->insertChildItem< _ItemComparator, true >( mModelForItemFunctions, mi ); \
2122  } \
2123  else \
2124  { \
2125  pParent->d_ptr->insertChildItem< _ItemComparator, false >( mModelForItemFunctions, mi ); \
2126  }
2127 
2128  // If pParent is viewable then the insertion call will also set the child state to viewable.
2129  // Since mi MAY have children, then this call may make them viewable.
2130  switch( mSortOrder->messageSorting() )
2131  {
2132  case SortOrder::SortMessagesByDateTime:
2133  INSERT_MESSAGE_WITH_COMPARATOR( ItemDateComparator )
2134  break;
2135  case SortOrder::SortMessagesByDateTimeOfMostRecent:
2136  INSERT_MESSAGE_WITH_COMPARATOR( ItemMaxDateComparator )
2137  break;
2138  case SortOrder::SortMessagesBySize:
2139  INSERT_MESSAGE_WITH_COMPARATOR( ItemSizeComparator )
2140  break;
2141  case SortOrder::SortMessagesBySenderOrReceiver:
2142  INSERT_MESSAGE_WITH_COMPARATOR( ItemSenderOrReceiverComparator )
2143  break;
2144  case SortOrder::SortMessagesBySender:
2145  INSERT_MESSAGE_WITH_COMPARATOR( ItemSenderComparator )
2146  break;
2147  case SortOrder::SortMessagesByReceiver:
2148  INSERT_MESSAGE_WITH_COMPARATOR( ItemReceiverComparator )
2149  break;
2150  case SortOrder::SortMessagesBySubject:
2151  INSERT_MESSAGE_WITH_COMPARATOR( ItemSubjectComparator )
2152  break;
2153  case SortOrder::SortMessagesByActionItemStatus:
2154  INSERT_MESSAGE_WITH_COMPARATOR( ItemActionItemStatusComparator )
2155  break;
2156  case SortOrder::SortMessagesByUnreadStatus:
2157  INSERT_MESSAGE_WITH_COMPARATOR( ItemUnreadStatusComparator )
2158  break;
2159  case SortOrder::SortMessagesByImportantStatus:
2160  INSERT_MESSAGE_WITH_COMPARATOR( ItemImportantStatusComparator )
2161  break;
2162  case SortOrder::NoMessageSorting:
2163  pParent->appendChildItem( mModelForItemFunctions, mi );
2164  break;
2165  default: // should never happen
2166  pParent->appendChildItem( mModelForItemFunctions, mi );
2167  break;
2168  }
2169 
2170  // Decide if we need to expand parents
2171  bool childNeedsExpanding = ( mi->initialExpandStatus() == Item::ExpandNeeded );
2172 
2173  if ( pParent->initialExpandStatus() == Item::NoExpandNeeded )
2174  {
2175  switch( mAggregation->threadExpandPolicy() )
2176  {
2177  case Aggregation::NeverExpandThreads:
2178  // just do nothing unless this child has children and is already marked for expansion
2179  if ( childNeedsExpanding )
2180  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2181  break;
2182  case Aggregation::ExpandThreadsWithNewMessages: // No more new status. fall through to unread if it exists in config
2183  case Aggregation::ExpandThreadsWithUnreadMessages:
2184  // expand only if unread (or it has children marked for expansion)
2185  if ( childNeedsExpanding || !mi->status().isRead() )
2186  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2187  break;
2188  case Aggregation::ExpandThreadsWithUnreadOrImportantMessages:
2189  // expand only if unread, important or todo (or it has children marked for expansion)
2190  // FIXME: Wouldn't it be nice to be able to test for bitmasks in MessageStatus ?
2191  if ( childNeedsExpanding || !mi->status().isRead() || mi->status().isImportant() || mi->status().isToAct() )
2192  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2193  break;
2194  case Aggregation::AlwaysExpandThreads:
2195  // expand everything
2196  pParent->setInitialExpandStatus( Item::ExpandNeeded );
2197  break;
2198  default:
2199  // BUG
2200  break;
2201  }
2202  } // else it's already marked for expansion or expansion has been already executed
2203 
2204  // expand parent first, if possible
2205  if ( pParent->initialExpandStatus() == Item::ExpandNeeded )
2206  {
2207  // If UI is not disconnected and parent is viewable, go up and expand
2208  if ( mModelForItemFunctions && pParent->isViewable() )
2209  {
2210  // Now expand parents as needed
2211  Item * parentToExpand = pParent;
2212  while ( parentToExpand )
2213  {
2214  if ( parentToExpand == mRootItem )
2215  break; // no need to set it expanded
2216  // parentToExpand is surely viewable (because this item is)
2217  if ( parentToExpand->initialExpandStatus() == Item::ExpandExecuted )
2218  break;
2219 
2220  mView->expand( q->index( parentToExpand, 0 ) );
2221 
2222  parentToExpand->setInitialExpandStatus( Item::ExpandExecuted );
2223  parentToExpand = parentToExpand->parent();
2224  }
2225  } else {
2226  // It isn't viewable or UI is disconnected: climb up marking only
2227  Item * parentToExpand = pParent->parent();
2228  while ( parentToExpand )
2229  {
2230  if ( parentToExpand == mRootItem )
2231  break; // no need to set it expanded
2232  parentToExpand->setInitialExpandStatus( Item::ExpandNeeded );
2233  parentToExpand = parentToExpand->parent();
2234  }
2235  }
2236  }
2237 
2238  if ( mi->isViewable() )
2239  {
2240  // mi is now viewable
2241 
2242  // sync subtree expanded status
2243  if ( childNeedsExpanding )
2244  {
2245  if ( mi->childItemCount() > 0 )
2246  if ( mModelForItemFunctions ) // the UI is not disconnected
2247  syncExpandedStateOfSubtree( mi ); // sync the real state in the view
2248  }
2249 
2250  // apply the filter, if needed
2251  if ( mFilter )
2252  {
2253  Q_ASSERT( mModelForItemFunctions ); // the UI must be NOT disconnected here
2254 
2255  // apply the filter to subtree
2256  if ( applyFilterToSubtree( mi, q->index( pParent, 0 ) ) )
2257  {
2258  // mi matched, expand parents (unconditionally)
2259  mView->ensureDisplayedWithParentsExpanded( mi );
2260  }
2261  }
2262  }
2263 
2264  // Now we need to propagate the property changes the upper levels.
2265 
2266  // If we have just inserted a message inside the root then no work needs to be done:
2267  // no grouping is in effect and the message is already in the right place.
2268  if ( pParent == mRootItem )
2269  return;
2270 
2271  // If we have just removed the item from this parent and re-inserted it
2272  // then this operation was a simple re-sort. The code above didn't update
2273  // the properties when removing the item so we don't actually need
2274  // to make the updates back.
2275  if ( oldParentWasTheSame )
2276  return;
2277 
2278  // FIXME: OPTIMIZE THIS: First propagate changes THEN syncExpandedStateOfSubtree()
2279  // and applyFilterToSubtree... (needs some thinking though).
2280 
2281  // Time to propagate up.
2282  propagateItemPropertiesToParent( mi );
2283 
2284  // Aaah.. we're done. Time for a thea ? :)
2285 }
2286 
2287 // FIXME: ThreadItem ?
2288 //
2289 // Foo Bar, Joe Thommason, Martin Rox ... Eddie Maiden <date of the thread>
2290 // Title <number of messages>, Last by xxx <inner status>
2291 //
2292 // When messages are added, mark it as dirty only (?)
2293 
2294 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass5( ViewItemJob *job, const QTime &tStart )
2295 {
2296  // In this pass we scan the group headers that are in mGroupHeadersThatNeedUpdate.
2297  // Empty groups get deleted while the other ones are re-sorted.
2298  int elapsed;
2299 
2300  int curIndex = job->currentIndex();
2301 
2302  QHash< GroupHeaderItem *, GroupHeaderItem * >::Iterator it = mGroupHeadersThatNeedUpdate.begin();
2303  QHash< GroupHeaderItem *, GroupHeaderItem * >::Iterator end = mGroupHeadersThatNeedUpdate.end();
2304 
2305  while ( it != end )
2306  {
2307  if ( ( *it )->childItemCount() == 0 )
2308  {
2309  // group with no children, kill it
2310  ( *it )->parent()->takeChildItem( mModelForItemFunctions, *it );
2311  mGroupHeaderItemHash.remove( ( *it )->label() );
2312 
2313  // If we were going to restore its position after the job step, well.. we can't do it anymore.
2314  if ( mCurrentItemToRestoreAfterViewItemJobStep == ( *it ) )
2315  mCurrentItemToRestoreAfterViewItemJobStep = 0;
2316 
2317  // bye bye
2318  delete *it;
2319  } else {
2320  // Group with children: probably needs re-sorting.
2321 
2322  // Re-sorting here is an expensive operation.
2323  // In fact groups have been put in the QHash above on the assumption
2324  // that re-sorting *might* be needed but no real (expensive) check
2325  // has been done yet. Also by sorting a single group we might actually
2326  // put the others in the right place.
2327  // So finally check if re-sorting is *really* needed.
2328  bool needsReSorting;
2329 
2330  // A macro really improves readability here.
2331 #define CHECK_IF_GROUP_NEEDS_RESORTING( _ItemDateComparator ) \
2332  switch ( mSortOrder->groupSortDirection() ) \
2333  { \
2334  case SortOrder::Ascending: \
2335  needsReSorting = ( *it )->parent()->d_ptr->childItemNeedsReSorting< _ItemDateComparator, true >( *it ); \
2336  break; \
2337  case SortOrder::Descending: \
2338  needsReSorting = ( *it )->parent()->d_ptr->childItemNeedsReSorting< _ItemDateComparator, false >( *it ); \
2339  break; \
2340  default: /* should never happen */ \
2341  needsReSorting = false; \
2342  break; \
2343  }
2344 
2345  switch ( mSortOrder->groupSorting() )
2346  {
2347  case SortOrder::SortGroupsByDateTime:
2348  CHECK_IF_GROUP_NEEDS_RESORTING( ItemDateComparator )
2349  break;
2350  case SortOrder::SortGroupsByDateTimeOfMostRecent:
2351  CHECK_IF_GROUP_NEEDS_RESORTING( ItemMaxDateComparator )
2352  break;
2353  case SortOrder::SortGroupsBySenderOrReceiver:
2354  CHECK_IF_GROUP_NEEDS_RESORTING( ItemSenderOrReceiverComparator )
2355  break;
2356  case SortOrder::SortGroupsBySender:
2357  CHECK_IF_GROUP_NEEDS_RESORTING( ItemSenderComparator )
2358  break;
2359  case SortOrder::SortGroupsByReceiver:
2360  CHECK_IF_GROUP_NEEDS_RESORTING( ItemReceiverComparator )
2361  break;
2362  case SortOrder::NoGroupSorting:
2363  needsReSorting = false;
2364  break;
2365  default:
2366  // Should never happen... just assume re-sorting is not needed
2367  needsReSorting = false;
2368  break;
2369  }
2370 
2371  if ( needsReSorting )
2372  attachGroup( *it ); // it will first detach and then re-attach in the proper place
2373  }
2374 
2375  it = mGroupHeadersThatNeedUpdate.erase( it );
2376 
2377  curIndex++;
2378 
2379  // FIXME: In fact a single update is likely to manipulate
2380  // a subtree with a LOT of messages inside. If interactivity is favored
2381  // we should check the time really more often.
2382  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2383  {
2384  elapsed = tStart.msecsTo( QTime::currentTime() );
2385  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2386  {
2387  if ( it != mGroupHeadersThatNeedUpdate.end() )
2388  {
2389  job->setCurrentIndex( curIndex );
2390  return ViewItemJobInterrupted;
2391  }
2392  }
2393  }
2394 
2395  }
2396 
2397  return ViewItemJobCompleted;
2398 }
2399 
2400 
2401 
2402 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass4( ViewItemJob *job, const QTime &tStart )
2403 {
2404  // In this pass we scan mUnassignedMessageListForPass4 which now
2405  // contains both items with parents and items without parents.
2406  // We scan mUnassignedMessageList for messages without parent (the ones that haven't been
2407  // attacched to the viewable tree yet) and find a suitable group for them. Then we simply
2408  // clear mUnassignedMessageList.
2409 
2410  // We call this pass "Grouping"
2411 
2412  int elapsed;
2413 
2414  int curIndex = job->currentIndex();
2415  int endIndex = job->endIndex();
2416 
2417  while ( curIndex <= endIndex )
2418  {
2419  MessageItem * mi = mUnassignedMessageListForPass4[curIndex];
2420  if ( !mi->parent() )
2421  {
2422  // Unassigned item: thread leader, insert into the proper group.
2423  // Locate the group (or root if no grouping requested)
2424  attachMessageToGroupHeader( mi );
2425  } else {
2426  // A parent was already assigned in Pass3: we have nothing to do here
2427  }
2428  curIndex++;
2429 
2430  // FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
2431  // a subtree with a LOT of messages inside. If interactivity is favored
2432  // we should check the time really more often.
2433  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2434  {
2435  elapsed = tStart.msecsTo( QTime::currentTime() );
2436  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2437  {
2438  if ( curIndex <= endIndex )
2439  {
2440  job->setCurrentIndex( curIndex );
2441  return ViewItemJobInterrupted;
2442  }
2443  }
2444  }
2445  }
2446 
2447  mUnassignedMessageListForPass4.clear();
2448  return ViewItemJobCompleted;
2449 }
2450 
2451 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass3( ViewItemJob *job, const QTime &tStart )
2452 {
2453  // In this pass we scan the mUnassignedMessageListForPass3 and try to do construct the threads
2454  // by using subject based threading. If subject based threading is not in effect then
2455  // this pass turns to a nearly-no-op: at the end of Pass2 we have swapped the lists
2456  // and mUnassignedMessageListForPass3 is actually empty.
2457 
2458  // We don't shrink the mUnassignedMessageListForPass3 for two reasons:
2459  // - It would mess up this chunked algorithm by shifting indexes
2460  // - mUnassignedMessageList is a QList which is basically an array. It's faster
2461  // to traverse an array of N entries than to remove K>0 entries one by one and
2462  // to traverse the remaining N-K entries.
2463 
2464  int elapsed;
2465 
2466  int curIndex = job->currentIndex();
2467  int endIndex = job->endIndex();
2468 
2469  while ( curIndex <= endIndex )
2470  {
2471  // If we're here, then threading is requested for sure.
2472  MessageItem * mi = mUnassignedMessageListForPass3[curIndex];
2473  if ( ( !mi->parent() ) || ( mi->threadingStatus() == MessageItem::ParentMissing ) )
2474  {
2475  // Parent is missing (either "physically" with the item being not attacched or "logically"
2476  // with the item being attacched to a group or directly to the root.
2477  if ( mi->subjectIsPrefixed() )
2478  {
2479  // We can try to guess it
2480  MessageItem * mparent = guessMessageParent( mi );
2481 
2482  if ( mparent )
2483  {
2484  // imperfect parent found
2485  if ( mi->isViewable() )
2486  {
2487  // mi was already viewable, we're just trying to re-parent it better...
2488  attachMessageToParent( mparent, mi );
2489  if ( !mparent->isViewable() )
2490  {
2491  // re-attach it immediately (so current item is not lost)
2492  MessageItem * topmost = mparent->topmostMessage();
2493  Q_ASSERT( !topmost->parent() ); // groups are always viewable!
2494  topmost->setThreadingStatus( MessageItem::ParentMissing );
2495  attachMessageToGroupHeader( topmost );
2496  }
2497  } else {
2498  // mi wasn't viewable yet.. no need to attach parent
2499  attachMessageToParent( mparent, mi );
2500  }
2501  // and we're done for now
2502  } else {
2503  // so parent not found, (threadingStatus() is either MessageItem::ParentMissing or MessageItem::NonThreadable)
2504  Q_ASSERT( ( mi->threadingStatus() == MessageItem::ParentMissing ) || ( mi->threadingStatus() == MessageItem::NonThreadable ) );
2505  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2506  // and wait for Pass4
2507  }
2508  } else {
2509  // can't guess the parent as the subject isn't prefixed
2510  Q_ASSERT( ( mi->threadingStatus() == MessageItem::ParentMissing ) || ( mi->threadingStatus() == MessageItem::NonThreadable ) );
2511  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2512  // and wait for Pass4
2513  }
2514  } else {
2515  // Has a parent: either perfect parent already found or non threadable.
2516  // Since we don't end here if mi has status of parent missing then mi must not have imperfect parent.
2517  Q_ASSERT( mi->threadingStatus() != MessageItem::ImperfectParentFound );
2518  Q_ASSERT( mi->isViewable() );
2519  }
2520 
2521  curIndex++;
2522 
2523  // FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
2524  // a subtree with a LOT of messages inside. If interactivity is favored
2525  // we should check the time really more often.
2526  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2527  {
2528  elapsed = tStart.msecsTo( QTime::currentTime() );
2529  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2530  {
2531  if ( curIndex <= endIndex )
2532  {
2533  job->setCurrentIndex( curIndex );
2534  return ViewItemJobInterrupted;
2535  }
2536  }
2537  }
2538  }
2539 
2540  mUnassignedMessageListForPass3.clear();
2541  return ViewItemJobCompleted;
2542 }
2543 
2544 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass2( ViewItemJob *job, const QTime &tStart )
2545 {
2546  // In this pass we scan the mUnassignedMessageList and try to do construct the threads.
2547  // If some thread leader message got attacched to the viewable tree in Pass1Fill then
2548  // we'll also attach all of its children too. The thread leaders we were unable
2549  // to attach in Pass1Fill and their children (which we find here) will make it to the small Pass3
2550 
2551  // We don't shrink the mUnassignedMessageList for two reasons:
2552  // - It would mess up this chunked algorithm by shifting indexes
2553  // - mUnassignedMessageList is a QList which is basically an array. It's faster
2554  // to traverse an array of N entries than to remove K>0 entries one by one and
2555  // to traverse the remaining N-K entries.
2556 
2557  // We call this pass "Threading"
2558 
2559  int elapsed;
2560 
2561  int curIndex = job->currentIndex();
2562  int endIndex = job->endIndex();
2563 
2564  while ( curIndex <= endIndex )
2565  {
2566  // If we're here, then threading is requested for sure.
2567  MessageItem * mi = mUnassignedMessageListForPass2[curIndex];
2568  // The item may or may not have a parent.
2569  // If it has no parent or it has a temporary one (mi->parent() && mi->threadingStatus() == MessageItem::ParentMissing)
2570  // then we attempt to (re-)thread it. Otherwise we just do nothing (the job has already been done by the previous steps).
2571  if ( ( !mi->parent() ) || ( mi->threadingStatus() == MessageItem::ParentMissing ) )
2572  {
2573  MessageItem * mparent = findMessageParent( mi );
2574 
2575  if ( mparent )
2576  {
2577  // parent found, either perfect or imperfect
2578  if ( mi->isViewable() )
2579  {
2580  // mi was already viewable, we're just trying to re-parent it better...
2581  attachMessageToParent( mparent, mi );
2582  if ( !mparent->isViewable() )
2583  {
2584  // re-attach it immediately (so current item is not lost)
2585  MessageItem * topmost = mparent->topmostMessage();
2586  Q_ASSERT( !topmost->parent() ); // groups are always viewable!
2587  topmost->setThreadingStatus( MessageItem::ParentMissing );
2588  attachMessageToGroupHeader( topmost );
2589  }
2590  } else {
2591  // mi wasn't viewable yet.. no need to attach parent
2592  attachMessageToParent( mparent, mi );
2593  }
2594  // and we're done for now
2595  } else {
2596  // so parent not found, (threadingStatus() is either MessageItem::ParentMissing or MessageItem::NonThreadable)
2597  switch( mi->threadingStatus() )
2598  {
2599  case MessageItem::ParentMissing:
2600  if ( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
2601  {
2602  // parent missing but still can be found in Pass3
2603  mUnassignedMessageListForPass3.append( mi ); // this is ~O(1)
2604  } else {
2605  // We're not doing subject based threading: will never be threaded, go straight to Pass4
2606  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2607  }
2608  break;
2609  case MessageItem::NonThreadable:
2610  // will never be threaded, go straight to Pass4
2611  mUnassignedMessageListForPass4.append( mi ); // this is ~O(1)
2612  break;
2613  default:
2614  // a bug for sure
2615  kWarning() << "ERROR: Invalid message threading status returned by findMessageParent()!";
2616  Q_ASSERT( false );
2617  break;
2618  }
2619  }
2620  } else {
2621  // Has a parent: either perfect parent already found or non threadable.
2622  // Since we don't end here if mi has status of parent missing then mi must not have imperfect parent.
2623  Q_ASSERT( mi->threadingStatus() != MessageItem::ImperfectParentFound );
2624  if ( !mi->isViewable() )
2625  {
2626  kWarning() << "Non viewable message " << mi << " subject " << mi->subject().toUtf8().data();
2627  Q_ASSERT( mi->isViewable() );
2628  }
2629  }
2630 
2631  curIndex++;
2632 
2633  // FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
2634  // a subtree with a LOT of messages inside. If interactivity is favored
2635  // we should check the time really more often.
2636  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2637  {
2638  elapsed = tStart.msecsTo( QTime::currentTime() );
2639  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2640  {
2641  if ( curIndex <= endIndex )
2642  {
2643  job->setCurrentIndex( curIndex );
2644  return ViewItemJobInterrupted;
2645  }
2646  }
2647  }
2648  }
2649 
2650  mUnassignedMessageListForPass2.clear();
2651  return ViewItemJobCompleted;
2652 }
2653 
2654 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Fill( ViewItemJob *job, const QTime &tStart )
2655 {
2656  // In this pass we scan the a contiguous region of the underlying storage (that is
2657  // assumed to be FLAT) and create the corresponding MessageItem objects.
2658  // The deal is to show items to the user as soon as possible so in this pass we
2659  // *TRY* to attach them to the viewable tree (which is rooted on mRootItem).
2660  // Messages we're unable to attach for some reason (mainly due to threading) get appended
2661  // to mUnassignedMessageList and wait for Pass2.
2662 
2663  // We call this pass "Processing"
2664 
2665  int elapsed;
2666 
2667  // Should we use the receiver or the sender field for sorting ?
2668  bool bUseReceiver = mStorageModelContainsOutboundMessages;
2669 
2670  // The begin storage index of our work
2671  int curIndex = job->currentIndex();
2672  // The end storage index of our work.
2673  int endIndex = job->endIndex();
2674 
2675  unsigned long msgToSelect = mPreSelectionMode == PreSelectLastSelected ? mStorageModel->preSelectedMessage() : 0;
2676 
2677  MessageItem * mi = 0;
2678 
2679  while( curIndex <= endIndex )
2680  {
2681  // Create the message item with no parent: we'll set it later
2682  if ( !mi )
2683  {
2684  mi = new MessageItem();
2685  } else {
2686  // a MessageItem discarded by a previous iteration: reuse it.
2687  Q_ASSERT( mi->parent() == 0 );
2688  }
2689 
2690  if ( !mStorageModel->initializeMessageItem( mi, curIndex, bUseReceiver ) )
2691  {
2692  // ugh
2693  kWarning() << "Fill of the MessageItem at storage row index " << curIndex << " failed";
2694  curIndex++;
2695  continue;
2696  }
2697 
2698  // If we're supposed to pre-select a specific message, check if it's this one.
2699  if ( msgToSelect != 0 && msgToSelect == mi->uniqueId() ) {
2700  // Found, it's this one.
2701  // But actually it's not viewable (so not selectable). We must wait
2702  // until the end of the job to be 100% sure. So here we just translate
2703  // the unique id to a MessageItem pointer and wait.
2704  mLastSelectedMessageInFolder = mi;
2705  msgToSelect = 0; // already found, don't bother checking anymore
2706  }
2707 
2708  // Update the newest/oldest message, since we might be supposed to select those later
2709  if ( !mOldestItem || mOldestItem->date() > mi->date() ) {
2710  mOldestItem = mi;
2711  }
2712  if ( !mNewestItem || mNewestItem->date() < mi->date() ) {
2713  mNewestItem = mi;
2714  }
2715 
2716  // Ok.. it passed the initial checks: we will not be discarding it.
2717  // Make this message item an invariant index to the underlying model storage.
2718  mInvariantRowMapper->createModelInvariantIndex( curIndex, mi );
2719 
2720 
2721  // Attempt to do threading as soon as possible (to display items to the user)
2722  if ( mAggregation->threading() != Aggregation::NoThreading )
2723  {
2724  // Threading is requested
2725 
2726  // Fetch the data needed for proper threading
2727  // Add the item to the threading caches
2728 
2729  switch( mAggregation->threading() )
2730  {
2731  case Aggregation::PerfectReferencesAndSubject:
2732  mStorageModel->fillMessageItemThreadingData( mi, curIndex, StorageModel::PerfectThreadingReferencesAndSubject );
2733 
2734  // We also need to build the subject-based threading cache
2735  addMessageToSubjectBasedThreadingCache( mi );
2736  break;
2737  case Aggregation::PerfectAndReferences:
2738  mStorageModel->fillMessageItemThreadingData( mi, curIndex, StorageModel::PerfectThreadingPlusReferences );
2739  break;
2740  default:
2741  mStorageModel->fillMessageItemThreadingData( mi, curIndex, StorageModel::PerfectThreadingOnly );
2742  break;
2743  }
2744 
2745  // Perfect/References threading cache
2746  mThreadingCacheMessageIdMD5ToMessageItem.insert( mi->messageIdMD5(), mi );
2747 
2748  // Check if this item is a perfect parent for some imperfectly threaded
2749  // message (that is actually attacched to it, but not necessairly to the
2750  // viewable root). If it is, then remove the imperfect child from its
2751  // current parent rebuild the hierarchy on the fly.
2752 
2753  bool needsImmediateReAttach = false;
2754 
2755  if ( mThreadingCacheMessageInReplyToIdMD5ToMessageItem.count() > 0 ) // unlikely
2756  {
2757  QList< MessageItem * > lImperfectlyThreaded = mThreadingCacheMessageInReplyToIdMD5ToMessageItem.values( mi->messageIdMD5() );
2758  if ( !lImperfectlyThreaded.isEmpty() )
2759  {
2760  // must move all of the items in the perfect parent
2761  QList< MessageItem * >::ConstIterator end( lImperfectlyThreaded.constEnd() );
2762  for ( QList< MessageItem * >::ConstIterator it = lImperfectlyThreaded.constBegin(); it != end; ++it )
2763  {
2764  Q_ASSERT( ( *it )->parent() );
2765  Q_ASSERT( ( *it )->parent() != mi );
2766 
2767  if ( !( ( (*it)->threadingStatus() == MessageItem::ImperfectParentFound ) ||
2768  ( (*it)->threadingStatus() == MessageItem::ParentMissing ) ) ) {
2769  kError() << "Got message " << (*it) << " with threading status" << (*it)->threadingStatus();
2770  Q_ASSERT_X( false, "ModelPrivate::viewItemJobStepInternalForJobPass1Fill", "Wrong threading status" );
2771  }
2772 
2773  // If the item was already attached to the view then
2774  // re-attach it immediately. This will avoid a message
2775  // being displayed for a short while in the view and then
2776  // disappear until a perfect parent isn't found.
2777  if ( ( *it )->isViewable() )
2778  needsImmediateReAttach = true;
2779 
2780  ( *it )->setThreadingStatus( MessageItem::PerfectParentFound );
2781  attachMessageToParent( mi, *it );
2782  }
2783  }
2784  }
2785 
2786  // FIXME: Might look by "References" too, here... (?)
2787 
2788  // Attempt to do threading with anything we already have in caches until now
2789  // Note that this is likely to work since thread-parent messages tend
2790  // to come before thread-children messages in the folders (simply because of
2791  // date of arrival).
2792 
2793  Item * pParent;
2794 
2795  // First of all try to find a "perfect parent", that is the message for that
2796  // we have the ID in the "In-Reply-To" field. This is actually done by using
2797  // MD5 caches of the message ids because of speed. Collisions are very unlikely.
2798 
2799  const QByteArray md5 = mi->inReplyToIdMD5();
2800 
2801  if ( !md5.isEmpty() )
2802  {
2803  // Have an In-Reply-To field MD5.
2804  // In well behaved mailing lists 70% of the threadable messages get a parent here :)
2805  pParent = mThreadingCacheMessageIdMD5ToMessageItem.value( md5, 0 );
2806 
2807  if( pParent ) // very likely
2808  {
2809  // Take care of self-referencing (which is always possible)
2810  // and circular In-Reply-To reference loops which are possible
2811  // in case this item was found to be a perfect parent for some
2812  // imperfectly threaded message just above.
2813  if (
2814  ( mi == pParent ) || // self referencing message
2815  (
2816  ( mi->childItemCount() > 0 ) && // mi already has children, this is fast to determine
2817  pParent->hasAncestor( mi ) // pParent is in the mi's children tree
2818  )
2819  )
2820  {
2821  // Bad, bad message.. it has In-Reply-To equal to Message-Id
2822  // or it's in a circular In-Reply-To reference loop.
2823  // Will wait for Pass2 with References-Id only
2824  kWarning() << "Circular In-Reply-To reference loop detected in the message tree";
2825  mUnassignedMessageListForPass2.append( mi );
2826  } else {
2827  // wow, got a perfect parent for this message!
2828  mi->setThreadingStatus( MessageItem::PerfectParentFound );
2829  attachMessageToParent( pParent, mi );
2830  // we're done with this message (also for Pass2)
2831  }
2832  } else {
2833  // got no parent
2834  // will have to wait Pass2
2835  mUnassignedMessageListForPass2.append( mi );
2836  }
2837  } else {
2838  // No In-Reply-To header.
2839 
2840  bool mightHaveOtherMeansForThreading;
2841 
2842  switch( mAggregation->threading() )
2843  {
2844  case Aggregation::PerfectReferencesAndSubject:
2845  mightHaveOtherMeansForThreading = mi->subjectIsPrefixed() || !mi->referencesIdMD5().isEmpty();
2846  break;
2847  case Aggregation::PerfectAndReferences:
2848  mightHaveOtherMeansForThreading = !mi->referencesIdMD5().isEmpty();
2849  break;
2850  case Aggregation::PerfectOnly:
2851  mightHaveOtherMeansForThreading = false;
2852  break;
2853  default:
2854  // BUG: there shouldn't be other values (NoThreading is excluded in an upper branch)
2855  Q_ASSERT( false );
2856  mightHaveOtherMeansForThreading = false; // make gcc happy
2857  break;
2858  }
2859 
2860  if ( mightHaveOtherMeansForThreading )
2861  {
2862  // We might have other means for threading this message, wait until Pass2
2863  mUnassignedMessageListForPass2.append( mi );
2864  } else {
2865  // No other means for threading this message. This is either
2866  // a standalone message or a thread leader.
2867  // If there is no grouping in effect or thread leaders are just the "topmost"
2868  // messages then we might be done with this one.
2869  if (
2870  ( mAggregation->grouping() == Aggregation::NoGrouping ) ||
2871  ( mAggregation->threadLeader() == Aggregation::TopmostMessage )
2872  )
2873  {
2874  // We're done with this message: it will be surely either toplevel (no grouping in effect)
2875  // or a thread leader with a well defined group. Do it :)
2876  //kDebug() << "Setting message status from " << mi->threadingStatus() << " to non threadable (1) " << mi;
2877  mi->setThreadingStatus( MessageItem::NonThreadable );
2878  // Locate the parent group for this item
2879  attachMessageToGroupHeader( mi );
2880  // we're done with this message (also for Pass2)
2881  } else {
2882  // Threads belong to the most recent message in the thread. This means
2883  // that we have to wait until Pass2 or Pass3 to assign a group.
2884  mUnassignedMessageListForPass2.append( mi );
2885  }
2886  }
2887  }
2888 
2889  if ( needsImmediateReAttach && !mi->isViewable() )
2890  {
2891  // The item gathered previously viewable children. They must be immediately
2892  // re-shown. So this item must currently be attached to the view.
2893  // This is a temporary measure: it will be probably still moved.
2894  MessageItem * topmost = mi->topmostMessage();
2895  Q_ASSERT( topmost->threadingStatus() == MessageItem::ParentMissing );
2896  attachMessageToGroupHeader( topmost );
2897  }
2898 
2899  } else {
2900  // else no threading requested: we don't even need Pass2
2901  // set not threadable status (even if it might be not true, but in this mode we don't care)
2902  //kDebug() << "Setting message status from " << mi->threadingStatus() << " to non threadable (2) " << mi;
2903  mi->setThreadingStatus( MessageItem::NonThreadable );
2904  // locate the parent group for this item
2905  if ( mAggregation->grouping() == Aggregation::NoGrouping )
2906  attachMessageToParent( mRootItem, mi ); // no groups requested, attach directly to root
2907  else
2908  attachMessageToGroupHeader( mi );
2909  // we're done with this message (also for Pass2)
2910  }
2911 
2912  mi = 0; // this item was pushed somewhere, create a new one at next iteration
2913  curIndex++;
2914 
2915  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
2916  {
2917  elapsed = tStart.msecsTo( QTime::currentTime() );
2918  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
2919  {
2920  if ( curIndex <= endIndex )
2921  {
2922  job->setCurrentIndex( curIndex );
2923  return ViewItemJobInterrupted;
2924  }
2925  }
2926  }
2927  }
2928 
2929  if ( mi )
2930  delete mi;
2931  return ViewItemJobCompleted;
2932 }
2933 
2934 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Cleanup( ViewItemJob *job, const QTime &tStart )
2935 {
2936  Q_ASSERT( mModelForItemFunctions ); // UI must be not disconnected here
2937  // In this pass we remove the MessageItem objects that are present in the job
2938  // and put their children in the unassigned message list.
2939 
2940  // Note that this list in fact contains MessageItem objects (we need dynamic_cast<>).
2941  QList< ModelInvariantIndex * > * invalidatedMessages = job->invariantIndexList();
2942 
2943  // We don't shrink the invalidatedMessages because it's basically an array.
2944  // It's faster to traverse an array of N entries than to remove K>0 entries
2945  // one by one and to traverse the remaining N-K entries.
2946 
2947  int elapsed;
2948 
2949  // The begin index of our work
2950  int curIndex = job->currentIndex();
2951  // The end index of our work.
2952  int endIndex = job->endIndex();
2953 
2954  if ( curIndex == job->startIndex() )
2955  Q_ASSERT( mOrphanChildrenHash.isEmpty() );
2956 
2957  while( curIndex <= endIndex )
2958  {
2959  // Get the underlying storage message data...
2960  MessageItem * dyingMessage = dynamic_cast< MessageItem * >( invalidatedMessages->at( curIndex ) );
2961  // This MUST NOT be null (otherwise we have a bug somewhere in this file).
2962  Q_ASSERT( dyingMessage );
2963 
2964  // If we were going to pre-select this message but we were interrupted
2965  // *before* it was actually made viewable, we just clear the pre-selection pointer
2966  // and unique id (abort pre-selection).
2967  if ( dyingMessage == mLastSelectedMessageInFolder )
2968  {
2969  mLastSelectedMessageInFolder = 0;
2970  mPreSelectionMode = PreSelectNone;
2971  }
2972 
2973  // remove the message from any pending user job
2974  if ( mPersistentSetManager )
2975  {
2976  mPersistentSetManager->removeMessageItemFromAllSets( dyingMessage );
2977  if ( mPersistentSetManager->setCount() < 1 )
2978  {
2979  delete mPersistentSetManager;
2980  mPersistentSetManager = 0;
2981  }
2982  }
2983 
2984  if ( dyingMessage->parent() )
2985  {
2986  // Handle saving the current selection: if this item was the current before the step
2987  // then zero it out. We have killed it and it's OK for the current item to change.
2988 
2989  if ( dyingMessage == mCurrentItemToRestoreAfterViewItemJobStep )
2990  {
2991  Q_ASSERT( dyingMessage->isViewable() );
2992  // Try to select the item below the removed one as it helps in doing a "readon" of emails:
2993  // you read a message, decide to delete it and then go to the next.
2994  // Qt tends to select the message above the removed one instead (this is a hardcoded logic in
2995  // QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved()).
2996  mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemAfter( dyingMessage, MessageTypeAny, false );
2997 
2998  if ( !mCurrentItemToRestoreAfterViewItemJobStep )
2999  {
3000  // There is no item below. Try the item above.
3001  // We still do it better than qt which tends to find the *thread* above
3002  // instead of the item above.
3003  mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemBefore( dyingMessage, MessageTypeAny, false );
3004  }
3005 
3006  Q_ASSERT( (!mCurrentItemToRestoreAfterViewItemJobStep) || mCurrentItemToRestoreAfterViewItemJobStep->isViewable() );
3007  }
3008 
3009  if (
3010  dyingMessage->isViewable() &&
3011  ( ( dyingMessage )->childItemCount() > 0 ) && // has children
3012  mView->isExpanded( q->index( dyingMessage, 0 ) ) // is actually expanded
3013  )
3014  saveExpandedStateOfSubtree( dyingMessage );
3015 
3016  Item * oldParent = dyingMessage->parent();
3017  oldParent->takeChildItem( q, dyingMessage );
3018 
3019  // FIXME: This can generate many message movements.. it would be nicer
3020  // to start from messages that are higher in the hierarchy so
3021  // we would need to move less stuff above.
3022 
3023  if ( oldParent != mRootItem )
3024  messageDetachedUpdateParentProperties( oldParent, dyingMessage );
3025 
3026  // We might have already removed its parent from the view, so it
3027  // might already be in the orphan child hash...
3028  if ( dyingMessage->threadingStatus() == MessageItem::ParentMissing )
3029  mOrphanChildrenHash.remove( dyingMessage ); // this can turn to a no-op (dyingMessage not present in fact)
3030 
3031  } else {
3032  // The dying message had no parent: this should happen only if it's already an orphan
3033 
3034  Q_ASSERT( dyingMessage->threadingStatus() == MessageItem::ParentMissing );
3035  Q_ASSERT( mOrphanChildrenHash.contains( dyingMessage ) );
3036  Q_ASSERT( dyingMessage != mCurrentItemToRestoreAfterViewItemJobStep );
3037 
3038  mOrphanChildrenHash.remove( dyingMessage );
3039  }
3040 
3041  if ( mAggregation->threading() != Aggregation::NoThreading )
3042  {
3043  // Threading is requested: remove the message from threading caches.
3044 
3045  // Remove from the cache of potential parent items
3046  mThreadingCacheMessageIdMD5ToMessageItem.remove( dyingMessage->messageIdMD5() );
3047 
3048  // If we also have a cache for subject-based threading then remove the message from there too
3049  if( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
3050  removeMessageFromSubjectBasedThreadingCache( dyingMessage );
3051 
3052  // If this message wasn't perfectly parented then it might still be in another cache.
3053  switch( dyingMessage->threadingStatus() )
3054  {
3055  case MessageItem::ImperfectParentFound:
3056  case MessageItem::ParentMissing:
3057  if ( !dyingMessage->inReplyToIdMD5().isEmpty() )
3058  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove( dyingMessage->inReplyToIdMD5() );
3059  break;
3060  default:
3061  Q_ASSERT( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( dyingMessage->inReplyToIdMD5(), dyingMessage ) );
3062  // make gcc happy
3063  break;
3064  }
3065  }
3066 
3067  while ( Item * childItem = dyingMessage->firstChildItem() )
3068  {
3069  MessageItem * childMessage = dynamic_cast< MessageItem * >( childItem );
3070  Q_ASSERT( childMessage );
3071 
3072  dyingMessage->takeChildItem( q, childMessage );
3073 
3074  if ( mAggregation->threading() != Aggregation::NoThreading )
3075  {
3076  if ( childMessage->threadingStatus() == MessageItem::PerfectParentFound )
3077  {
3078  // If the child message was perfectly parented then now it had
3079  // lost its perfect parent. Add to the cache of imperfectly parented.
3080  if ( !childMessage->inReplyToIdMD5().isEmpty() )
3081  {
3082  Q_ASSERT( !mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains( childMessage->inReplyToIdMD5(), childMessage ) );
3083  mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert( childMessage->inReplyToIdMD5(), childMessage );
3084  }
3085  }
3086  }
3087 
3088  // Parent is gone
3089  childMessage->setThreadingStatus( MessageItem::ParentMissing );
3090 
3091  // If the child (or any message in its subtree) is going to be selected,
3092  // then we must immediately reattach it to a temporary group in order for the
3093  // selection to be preserved across multiple steps. Otherwise we could end
3094  // with the child-to-be-selected being non viewable at the end
3095  // of the view job step. Attach to a temporary group.
3096  if (
3097  // child is going to be re-selected
3098  ( childMessage == mCurrentItemToRestoreAfterViewItemJobStep ) ||
3099  (
3100  // there is a message that is going to be re-selected
3101  mCurrentItemToRestoreAfterViewItemJobStep &&
3102  // that message is in the childMessage subtree
3103  mCurrentItemToRestoreAfterViewItemJobStep->hasAncestor( childMessage )
3104  )
3105  )
3106  {
3107  attachMessageToGroupHeader( childMessage );
3108 
3109  Q_ASSERT( childMessage->isViewable() );
3110  }
3111 
3112  mOrphanChildrenHash.insert( childMessage, childMessage );
3113  }
3114 
3115  if ( mNewestItem == dyingMessage ) {
3116  mNewestItem = 0;
3117  }
3118  if ( mOldestItem == dyingMessage ) {
3119  mOldestItem = 0;
3120  }
3121 
3122  delete dyingMessage;
3123 
3124  curIndex++;
3125 
3126  // FIXME: Maybe we should check smaller steps here since the
3127  // code above can generate large message tree movements
3128  // for each single item we sweep in the invalidatedMessages list.
3129  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
3130  {
3131  elapsed = tStart.msecsTo( QTime::currentTime() );
3132  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3133  {
3134  if ( curIndex <= endIndex )
3135  {
3136  job->setCurrentIndex( curIndex );
3137  return ViewItemJobInterrupted;
3138  }
3139  }
3140  }
3141  }
3142 
3143  // We looped over the entire deleted message list.
3144 
3145  job->setCurrentIndex( endIndex + 1 );
3146 
3147  // A quick last cleaning pass: this is usually very fast so we don't have a real
3148  // Pass enumeration for it. We just include it as trailer of Pass1Cleanup to be executed
3149  // when job->currentIndex() > job->endIndex();
3150 
3151  // We move all the messages from the orphan child hash to the unassigned message
3152  // list and get them ready for the standard Pass2.
3153 
3154  QHash< MessageItem *, MessageItem * >::Iterator it = mOrphanChildrenHash.begin();
3155  QHash< MessageItem *, MessageItem * >::Iterator end = mOrphanChildrenHash.end();
3156 
3157  curIndex = 0;
3158 
3159  while ( it != end )
3160  {
3161  mUnassignedMessageListForPass2.append( *it );
3162 
3163  it = mOrphanChildrenHash.erase( it );
3164 
3165  // This is still interruptible
3166 
3167  curIndex++;
3168 
3169  // FIXME: We could take "larger" steps here
3170  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
3171  {
3172  elapsed = tStart.msecsTo( QTime::currentTime() );
3173  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3174  {
3175  if ( it != mOrphanChildrenHash.end() )
3176  return ViewItemJobInterrupted;
3177  }
3178  }
3179  }
3180 
3181  return ViewItemJobCompleted;
3182 }
3183 
3184 
3185 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Update( ViewItemJob *job, const QTime &tStart )
3186 {
3187  Q_ASSERT( mModelForItemFunctions ); // UI must be not disconnected here
3188 
3189  // In this pass we simply update the MessageItem objects that are present in the job.
3190 
3191  // Note that this list in fact contains MessageItem objects (we need dynamic_cast<>).
3192  QList< ModelInvariantIndex * > * messagesThatNeedUpdate = job->invariantIndexList();
3193 
3194  // We don't shrink the messagesThatNeedUpdate because it's basically an array.
3195  // It's faster to traverse an array of N entries than to remove K>0 entries
3196  // one by one and to traverse the remaining N-K entries.
3197 
3198  int elapsed;
3199 
3200  // The begin index of our work
3201  int curIndex = job->currentIndex();
3202  // The end index of our work.
3203  int endIndex = job->endIndex();
3204 
3205  while( curIndex <= endIndex )
3206  {
3207  // Get the underlying storage message data...
3208  MessageItem * message = dynamic_cast< MessageItem * >( messagesThatNeedUpdate->at( curIndex ) );
3209  // This MUST NOT be null (otherwise we have a bug somewhere in this file).
3210  Q_ASSERT( message );
3211 
3212  int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow( message );
3213 
3214  if ( row < 0 )
3215  {
3216  // Must have been invalidated (so it's basically about to be deleted)
3217  Q_ASSERT( !message->isValid() );
3218  // Skip it here.
3219  curIndex++;
3220  continue;
3221  }
3222 
3223  time_t prevDate = message->date();
3224  time_t prevMaxDate = message->maxDate();
3225  bool toDoStatus = message->status().isToAct();
3226  bool prevUnreadStatus = !message->status().isRead();
3227  bool prevImportantStatus = message->status().isImportant();
3228 
3229  // The subject based threading cache is sorted by date: we must remove
3230  // the item and re-insert it since updateMessageItemData() may change the date too.
3231  if( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
3232  removeMessageFromSubjectBasedThreadingCache( message );
3233 
3234  // Do update
3235  mStorageModel->updateMessageItemData( message, row );
3236  QModelIndex idx = q->index( message, 0 );
3237  emit q->dataChanged( idx, idx );
3238 
3239  // Reinsert the item to the cache, if needed
3240  if( mAggregation->threading() == Aggregation::PerfectReferencesAndSubject )
3241  addMessageToSubjectBasedThreadingCache( message );
3242 
3243 
3244  int propertyChangeMask = 0;
3245 
3246  if ( prevDate != message->date() )
3247  propertyChangeMask |= DateChanged;
3248  if ( prevMaxDate != message->maxDate() )
3249  propertyChangeMask |= MaxDateChanged;
3250  if ( toDoStatus != message->status().isToAct() )
3251  propertyChangeMask |= ActionItemStatusChanged;
3252  if ( prevUnreadStatus != ( !message->status().isRead() ) )
3253  propertyChangeMask |= UnreadStatusChanged;
3254  if ( prevImportantStatus != ( !message->status().isImportant() ) )
3255  propertyChangeMask |= ImportantStatusChanged;
3256 
3257  if ( propertyChangeMask )
3258  {
3259  // Some message data has changed
3260  // now we need to handle the changes that might cause re-grouping/re-sorting
3261  // and propagate them to the parents.
3262 
3263  Item * pParent = message->parent();
3264 
3265  if ( pParent && ( pParent != mRootItem ) )
3266  {
3267  // The following function will return true if itemParent may be affected by the change.
3268  // If the itemParent isn't affected, we stop climbing.
3269  if ( handleItemPropertyChanges( propertyChangeMask, pParent, message ) )
3270  {
3271  Q_ASSERT( message->parent() ); // handleItemPropertyChanges() must never leave an item detached
3272 
3273  // Note that actually message->parent() may be different than pParent since
3274  // handleItemPropertyChanges() may have re-grouped it.
3275 
3276  // Time to propagate up.
3277  propagateItemPropertiesToParent( message );
3278  }
3279  } // else there is no parent so the item isn't attached to the view: re-grouping/re-sorting not needed.
3280  } // else message data didn't change an there is nothing interesting to do
3281 
3282  // (re-)apply the filter, if needed
3283  if ( mFilter && message->isViewable() )
3284  {
3285  // In all the other cases we (re-)apply the filter to the topmost subtree that this message is in.
3286  Item * pTopMostNonRoot = message->topmostNonRoot();
3287 
3288  Q_ASSERT( pTopMostNonRoot );
3289  Q_ASSERT( pTopMostNonRoot != mRootItem );
3290  Q_ASSERT( pTopMostNonRoot->parent() == mRootItem );
3291 
3292  // FIXME: The call below works, but it's expensive when we are updating
3293  // a lot of items with filtering enabled. This is because the updated
3294  // items are likely to be in the same subtree which we then filter multiple times.
3295  // A point for us is that when filtering there shouldn't be really many
3296  // items in the view so the user isn't going to update a lot of them at once...
3297  // Well... anyway, the alternative would be to write yet another
3298  // specialized routine that would update only the "message" item
3299  // above and climb up eventually hiding parents (without descending the sibling subtrees again).
3300  // If people complain about performance in this particular case I'll consider that solution.
3301 
3302  applyFilterToSubtree( pTopMostNonRoot, QModelIndex() );
3303 
3304  } // otherwise there is no filter or the item isn't viewable: very likely
3305  // left detached while propagating property changes. Will filter it
3306  // on reattach.
3307 
3308  // Done updating this message
3309 
3310  curIndex++;
3311 
3312  // FIXME: Maybe we should check smaller steps here since the
3313  // code above can generate large message tree movements
3314  // for each single item we sweep in the messagesThatNeedUpdate list.
3315  if ( ( curIndex % mViewItemJobStepMessageCheckCount ) == 0 )
3316  {
3317  elapsed = tStart.msecsTo( QTime::currentTime() );
3318  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3319  {
3320  if ( curIndex <= endIndex )
3321  {
3322  job->setCurrentIndex( curIndex );
3323  return ViewItemJobInterrupted;
3324  }
3325  }
3326  }
3327  }
3328 
3329  return ViewItemJobCompleted;
3330 }
3331 
3332 
3333 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJob( ViewItemJob *job, const QTime &tStart )
3334 {
3335  // This function does a timed chunk of work for a single Fill View job.
3336  // It attempts to process messages until a timeout forces it to return to the caller.
3337 
3338  // A macro would improve readability here but since this is a good point
3339  // to place debugger breakpoints then we need it explicited.
3340  // A (template) helper would need to pass many parameters and would not be inlined...
3341 
3342  int elapsed;
3343 
3344  if ( job->currentPass() == ViewItemJob::Pass1Fill )
3345  {
3346  // We're in Pass1Fill of the job.
3347  switch ( viewItemJobStepInternalForJobPass1Fill( job, tStart ) )
3348  {
3349  case ViewItemJobInterrupted:
3350  // current job interrupted by timeout: propagate status to caller
3351  return ViewItemJobInterrupted;
3352  break;
3353  case ViewItemJobCompleted:
3354  // pass 1 has been completed
3355  // # TODO: Refactor this, make it virtual or whatever, but switch == bad, code duplication etc
3356  job->setCurrentPass( ViewItemJob::Pass2 );
3357  job->setStartIndex( 0 );
3358  job->setEndIndex( mUnassignedMessageListForPass2.count() - 1 );
3359  // take care of small jobs which never timeout by themselves because
3360  // of a small number of messages. At the end of each job check
3361  // the time used and if we're timeoutting and there is another job
3362  // then interrupt.
3363  elapsed = tStart.msecsTo( QTime::currentTime() );
3364  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3365  {
3366  return ViewItemJobInterrupted;
3367  } // else proceed with the next pass
3368  break;
3369  default:
3370  // This is *really* a BUG
3371  kWarning() << "ERROR: returned an invalid result";
3372  Q_ASSERT( false );
3373  break;
3374  }
3375  } else if ( job->currentPass() == ViewItemJob::Pass1Cleanup )
3376  {
3377  // We're in Pass1Cleanup of the job.
3378  switch ( viewItemJobStepInternalForJobPass1Cleanup( job, tStart ) )
3379  {
3380  case ViewItemJobInterrupted:
3381  // current job interrupted by timeout: propagate status to caller
3382  return ViewItemJobInterrupted;
3383  break;
3384  case ViewItemJobCompleted:
3385  // pass 1 has been completed
3386  job->setCurrentPass( ViewItemJob::Pass2 );
3387  job->setStartIndex( 0 );
3388  job->setEndIndex( mUnassignedMessageListForPass2.count() - 1 );
3389  // take care of small jobs which never timeout by themselves because
3390  // of a small number of messages. At the end of each job check
3391  // the time used and if we're timeoutting and there is another job
3392  // then interrupt.
3393  elapsed = tStart.msecsTo( QTime::currentTime() );
3394  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3395  {
3396  return ViewItemJobInterrupted;
3397  } // else proceed with the next pass
3398  break;
3399  default:
3400  // This is *really* a BUG
3401  kWarning() << "ERROR: returned an invalid result";
3402  Q_ASSERT( false );
3403  break;
3404  }
3405  } else if ( job->currentPass() == ViewItemJob::Pass1Update )
3406  {
3407  // We're in Pass1Update of the job.
3408  switch ( viewItemJobStepInternalForJobPass1Update( job, tStart ) )
3409  {
3410  case ViewItemJobInterrupted:
3411  // current job interrupted by timeout: propagate status to caller
3412  return ViewItemJobInterrupted;
3413  break;
3414  case ViewItemJobCompleted:
3415  // pass 1 has been completed
3416  // Since Pass2, Pass3 and Pass4 are empty for an Update operation
3417  // we simply skip them. (TODO: Triple-verify this assertion...).
3418  job->setCurrentPass( ViewItemJob::Pass5 );
3419  job->setStartIndex( 0 );
3420  job->setEndIndex( mGroupHeadersThatNeedUpdate.count() - 1 );
3421  // take care of small jobs which never timeout by themselves because
3422  // of a small number of messages. At the end of each job check
3423  // the time used and if we're timeoutting and there is another job
3424  // then interrupt.
3425  elapsed = tStart.msecsTo( QTime::currentTime() );
3426  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3427  {
3428  return ViewItemJobInterrupted;
3429  } // else proceed with the next pass
3430  break;
3431  default:
3432  // This is *really* a BUG
3433  kWarning() << "ERROR: returned an invalid result";
3434  Q_ASSERT( false );
3435  break;
3436  }
3437  }
3438 
3439  // Pass1Fill/Pass1Cleanup/Pass1Update has been already completed.
3440 
3441  if ( job->currentPass() == ViewItemJob::Pass2 )
3442  {
3443  // We're in Pass2 of the job.
3444  switch ( viewItemJobStepInternalForJobPass2( job, tStart ) )
3445  {
3446  case ViewItemJobInterrupted:
3447  // current job interrupted by timeout: propagate status to caller
3448  return ViewItemJobInterrupted;
3449  break;
3450  case ViewItemJobCompleted:
3451  // pass 2 has been completed
3452  job->setCurrentPass( ViewItemJob::Pass3 );
3453  job->setStartIndex( 0 );
3454  job->setEndIndex( mUnassignedMessageListForPass3.count() - 1 );
3455  // take care of small jobs which never timeout by themselves because
3456  // of a small number of messages. At the end of each job check
3457  // the time used and if we're timeoutting and there is another job
3458  // then interrupt.
3459  elapsed = tStart.msecsTo( QTime::currentTime() );
3460  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3461  return ViewItemJobInterrupted;
3462  // else proceed with the next pass
3463  break;
3464  default:
3465  // This is *really* a BUG
3466  kWarning() << "ERROR: returned an invalid result";
3467  Q_ASSERT( false );
3468  break;
3469  }
3470  }
3471 
3472  if ( job->currentPass() == ViewItemJob::Pass3 )
3473  {
3474  // We're in Pass3 of the job.
3475  switch ( viewItemJobStepInternalForJobPass3( job, tStart ) )
3476  {
3477  case ViewItemJobInterrupted:
3478  // current job interrupted by timeout: propagate status to caller
3479  return ViewItemJobInterrupted;
3480  break;
3481  case ViewItemJobCompleted:
3482  // pass 3 has been completed
3483  job->setCurrentPass( ViewItemJob::Pass4 );
3484  job->setStartIndex( 0 );
3485  job->setEndIndex( mUnassignedMessageListForPass4.count() - 1 );
3486  // take care of small jobs which never timeout by themselves because
3487  // of a small number of messages. At the end of each job check
3488  // the time used and if we're timeoutting and there is another job
3489  // then interrupt.
3490  elapsed = tStart.msecsTo( QTime::currentTime() );
3491  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3492  return ViewItemJobInterrupted;
3493  // else proceed with the next pass
3494  break;
3495  default:
3496  // This is *really* a BUG
3497  kWarning() << "ERROR: returned an invalid result";
3498  Q_ASSERT( false );
3499  break;
3500  }
3501  }
3502 
3503  if ( job->currentPass() == ViewItemJob::Pass4 )
3504  {
3505  // We're in Pass4 of the job.
3506  switch ( viewItemJobStepInternalForJobPass4( job, tStart ) )
3507  {
3508  case ViewItemJobInterrupted:
3509  // current job interrupted by timeout: propagate status to caller
3510  return ViewItemJobInterrupted;
3511  break;
3512  case ViewItemJobCompleted:
3513  // pass 4 has been completed
3514  job->setCurrentPass( ViewItemJob::Pass5 );
3515  job->setStartIndex( 0 );
3516  job->setEndIndex( mGroupHeadersThatNeedUpdate.count() - 1 );
3517  // take care of small jobs which never timeout by themselves because
3518  // of a small number of messages. At the end of each job check
3519  // the time used and if we're timeoutting and there is another job
3520  // then interrupt.
3521  elapsed = tStart.msecsTo( QTime::currentTime() );
3522  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3523  return ViewItemJobInterrupted;
3524  // else proceed with the next pass
3525  break;
3526  default:
3527  // This is *really* a BUG
3528  kWarning() << "ERROR: returned an invalid result";;
3529  Q_ASSERT( false );
3530  break;
3531  }
3532  }
3533 
3534  // Pass4 has been already completed. Proceed to Pass5.
3535  return viewItemJobStepInternalForJobPass5( job, tStart );
3536 }
3537 
3538 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3539 
3540 // Namespace to collect all the vars and functions for KDEPIM_FOLDEROPEN_PROFILE
3541 namespace Stats {
3542 
3543 // Number of existing jobs/passes
3544 static const int numberOfPasses = ViewItemJob::LastIndex;
3545 
3546 // The pass in the last call of viewItemJobStepInternal(), used to detect when
3547 // a new pass starts
3548 static int lastPass = -1;
3549 
3550 // Total number of messages in the folder
3551 static int totalMessages;
3552 
3553 // Per-Job data
3554 static int numElements[numberOfPasses];
3555 static int totalTime[numberOfPasses];
3556 static int chunks[numberOfPasses];
3557 
3558 // Time, in msecs for some special operations
3559 static int expandingTreeTime;
3560 static int layoutChangeTime;
3561 
3562 // Descriptions of the job, for nicer debug output
3563 static const char *jobDescription[numberOfPasses] = {
3564  "Creating items from messages and simple threading",
3565  "Removing messages",
3566  "Updating messages",
3567  "Additional Threading",
3568  "Subject-Based threading",
3569  "Grouping",
3570  "Group resorting + cleanup"
3571 };
3572 
3573 // Timer to track time between start of first job and end of last job
3574 static QTime firstStartTime;
3575 
3576 // Timer to track time the current job takes
3577 static QTime currentJobStartTime;
3578 
3579 // Zeros the stats, to be called when the first job starts
3580 static void resetStats()
3581 {
3582  totalMessages = 0;
3583  layoutChangeTime = 0;
3584  expandingTreeTime = 0;
3585  lastPass = -1;
3586  for ( int i = 0; i < numberOfPasses; ++i ) {
3587  numElements[i] = 0;
3588  totalTime[i] = 0;
3589  chunks[i] = 0;
3590  }
3591 }
3592 
3593 } // namespace Stats
3594 
3595 void ModelPrivate::printStatistics()
3596 {
3597  using namespace Stats;
3598  int totalTotalTime = 0;
3599  int completeTime = firstStartTime.elapsed();
3600  for ( int i = 0; i < numberOfPasses; ++i )
3601  totalTotalTime += totalTime[i];
3602 
3603  float msgPerSecond = totalMessages / ( totalTotalTime / 1000.0f );
3604  float msgPerSecondComplete = totalMessages / ( completeTime / 1000.0f );
3605 
3606  int messagesWithSameSubjectAvg = 0;
3607  int messagesWithSameSubjectMax = 0;
3608  foreach( const QList< MessageItem * > *messages, mThreadingCacheMessageSubjectMD5ToMessageItem ) {
3609  if ( messages->size() > messagesWithSameSubjectMax )
3610  messagesWithSameSubjectMax = messages->size();
3611  messagesWithSameSubjectAvg += messages->size();
3612  }
3613  messagesWithSameSubjectAvg = messagesWithSameSubjectAvg / (float)mThreadingCacheMessageSubjectMD5ToMessageItem.size();
3614 
3615  int totalThreads = 0;
3616  if ( !mGroupHeaderItemHash.isEmpty() ) {
3617  foreach( const GroupHeaderItem *groupHeader, mGroupHeaderItemHash ) {
3618  totalThreads += groupHeader->childItemCount();
3619  }
3620  }
3621  else
3622  totalThreads = mRootItem->childItemCount();
3623 
3624  kDebug() << "Finished filling the view with" << totalMessages << "messages";
3625  kDebug() << "That took" << totalTotalTime << "msecs inside the model and"
3626  << completeTime << "in total.";
3627  kDebug() << ( totalTotalTime / (float) completeTime ) * 100.0f
3628  << "percent of the time was spent in the model.";
3629  kDebug() << "Time for layoutChanged(), in msecs:" << layoutChangeTime
3630  << "(" << (layoutChangeTime / (float)totalTotalTime) * 100.0f << "percent )";
3631  kDebug() << "Time to expand tree, in msecs:" << expandingTreeTime
3632  << "(" << (expandingTreeTime / (float)totalTotalTime) * 100.0f << "percent )";
3633  kDebug() << "Number of messages per second in the model:" << msgPerSecond;
3634  kDebug() << "Number of messages per second in total:" << msgPerSecondComplete;
3635  kDebug() << "Number of threads:" << totalThreads;
3636  kDebug() << "Number of groups:" << mGroupHeaderItemHash.size();
3637  kDebug() << "Messages per thread:" << totalMessages / (float)totalThreads;
3638  kDebug() << "Threads per group:" << totalThreads / (float)mGroupHeaderItemHash.size();
3639  kDebug() << "Messages with the same subject:"
3640  << "Max:" << messagesWithSameSubjectMax
3641  << "Avg:" << messagesWithSameSubjectAvg;
3642  kDebug();
3643  kDebug() << "Now follows a breakdown of the jobs.";
3644  kDebug();
3645  for ( int i = 0; i < numberOfPasses; ++i ) {
3646  if ( totalTime[i] == 0 )
3647  continue;
3648  float elementsPerSecond = numElements[i] / ( totalTime[i] / 1000.0f );
3649  float percent = totalTime[i] / (float)totalTotalTime * 100.0f;
3650  kDebug() << "----------------------------------------------";
3651  kDebug() << "Job" << i + 1 << "(" << jobDescription[i] << ")";
3652  kDebug() << "Share of complete time:" << percent << "percent";
3653  kDebug() << "Time in msecs:" << totalTime[i];
3654  kDebug() << "Number of elements:" << numElements[i]; // TODO: map of element string
3655  kDebug() << "Elements per second:" << elementsPerSecond;
3656  kDebug() << "Number of chunks:" << chunks[i];
3657  kDebug();
3658  }
3659 
3660  kDebug() << "==========================================================";
3661  resetStats();
3662 }
3663 
3664 #endif
3665 
3666 ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternal()
3667 {
3668  // This function does a timed chunk of work in our View Fill operation.
3669  // It attempts to do processing until it either runs out of jobs
3670  // to be done or a timeout forces it to interrupt and jump back to the caller.
3671 
3672  QTime tStart = QTime::currentTime();
3673  int elapsed;
3674 
3675  while( !mViewItemJobs.isEmpty() )
3676  {
3677  // Have a job to do.
3678  ViewItemJob * job = mViewItemJobs.first();
3679 
3680 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3681 
3682  // Here we check if an old job has just completed or if we are at the start of the
3683  // first job. We then initialize job data stuff and timers based on this.
3684 
3685  const int currentPass = job->currentPass();
3686  const bool firstChunk = currentPass != Stats::lastPass;
3687  if ( currentPass != Stats::lastPass && Stats::lastPass != -1 ) {
3688  Stats::totalTime[Stats::lastPass] = Stats::currentJobStartTime.elapsed();
3689  }
3690  const bool firstJob = job->currentPass() == ViewItemJob::Pass1Fill && firstChunk;
3691  const int elements = job->endIndex() - job->startIndex();
3692  if ( firstJob ) {
3693  Stats::resetStats();
3694  Stats::totalMessages = elements;
3695  Stats::firstStartTime.restart();
3696  }
3697  if ( firstChunk ) {
3698  Stats::numElements[currentPass] = elements;
3699  Stats::currentJobStartTime.restart();
3700  }
3701  Stats::chunks[currentPass]++;
3702  Stats::lastPass = currentPass;
3703 
3704 #endif
3705 
3706  mViewItemJobStepIdleInterval = job->idleInterval();
3707  mViewItemJobStepChunkTimeout = job->chunkTimeout();
3708  mViewItemJobStepMessageCheckCount = job->messageCheckCount();
3709 
3710  if ( job->disconnectUI() )
3711  {
3712  mModelForItemFunctions = 0; // disconnect the UI for this job
3713  Q_ASSERT( mLoading ); // this must be true in the first job
3714  // FIXME: Should assert yet more that this is the very first job for this StorageModel
3715  // Asserting only mLoading is not enough as we could be using a two-jobs loading strategy
3716  // or this could be a job enqueued before the first job has completed.
3717  } else {
3718  // With a connected UI we need to avoid the view to update the scrollbars at EVERY insertion or expansion.
3719  // QTreeViewPrivate::updateScrollBars() is very expensive as it loops through ALL the items in the view every time.
3720  // We can't disable the function directly as it's hidden in the private data object of QTreeView
3721  // but we can disable the parent QTreeView::updateGeometries() instead.
3722  // We will trigger it "manually" at the end of the step.
3723  mView->ignoreUpdateGeometries( true );
3724 
3725  // Ok.. I know that this seems unbelieveable but disabling updates actually
3726  // causes a (significant) performance loss in most cases. This is probably because QTreeView
3727  // uses delayed layouts when updates are disabled which should be delayed but in
3728  // fact are "forced" by next item insertions. The delayed layout algorithm, then
3729  // is probably slower than the non-delayed one.
3730  // Disabling the paintEvent() doesn't seem to work either.
3731  //mView->setUpdatesEnabled( false );
3732  }
3733 
3734  switch( viewItemJobStepInternalForJob( job, tStart ) )
3735  {
3736  case ViewItemJobInterrupted:
3737  {
3738  // current job interrupted by timeout: will propagate status to caller
3739  // but before this, give some feedback to the user
3740 
3741  // FIXME: This is now inaccurate, think of something else
3742  switch( job->currentPass() )
3743  {
3744  case ViewItemJob::Pass1Fill:
3745  case ViewItemJob::Pass1Cleanup:
3746  case ViewItemJob::Pass1Update:
3747  emit q->statusMessage( i18np( "Processed 1 Message of %2",
3748  "Processed %1 Messages of %2",
3749  job->currentIndex() - job->startIndex(),
3750  job->endIndex() - job->startIndex() + 1 ) );
3751  break;
3752  case ViewItemJob::Pass2:
3753  emit q->statusMessage( i18np( "Threaded 1 Message of %2",
3754  "Threaded %1 Messages of %2",
3755  job->currentIndex() - job->startIndex(),
3756  job->endIndex() - job->startIndex() + 1 ) );
3757  break;
3758  case ViewItemJob::Pass3:
3759  emit q->statusMessage( i18np( "Threaded 1 Message of %2",
3760  "Threaded %1 Messages of %2",
3761  job->currentIndex() - job->startIndex(),
3762  job->endIndex() - job->startIndex() + 1 ) );
3763  break;
3764  case ViewItemJob::Pass4:
3765  emit q->statusMessage( i18np( "Grouped 1 Thread of %2",
3766  "Grouped %1 Threads of %2",
3767  job->currentIndex() - job->startIndex(),
3768  job->endIndex() - job->startIndex() + 1 ) );
3769  break;
3770  case ViewItemJob::Pass5:
3771  emit q->statusMessage( i18np( "Updated 1 Group of %2",
3772  "Updated %1 Groups of %2",
3773  job->currentIndex() - job->startIndex(),
3774  job->endIndex() - job->startIndex() + 1 ) );
3775  break;
3776  default: break;
3777  }
3778 
3779  if( !job->disconnectUI() )
3780  {
3781  mView->ignoreUpdateGeometries( false );
3782  // explicit call to updateGeometries() here
3783  mView->updateGeometries();
3784  }
3785 
3786  return ViewItemJobInterrupted;
3787  }
3788  break;
3789  case ViewItemJobCompleted:
3790 
3791  // If this job worked with a disconnected UI, emit layoutChanged()
3792  // to reconnect it. We go back to normal operation now.
3793  if ( job->disconnectUI() )
3794  {
3795  mModelForItemFunctions = q;
3796  // This call would destroy the expanded state of items.
3797  // This is why when mModelForItemFunctions was 0 we didn't actually expand them
3798  // but we just set a "ExpandNeeded" mark...
3799 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3800  QTime layoutChangedTimer;
3801  layoutChangedTimer.start();
3802 #endif
3803  mView->modelAboutToEmitLayoutChanged();
3804  emit q->layoutChanged();
3805  mView->modelEmittedLayoutChanged();
3806 
3807 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3808  Stats::layoutChangeTime = layoutChangedTimer.elapsed();
3809  QTime expandingTime;
3810  expandingTime.start();
3811 #endif
3812 
3813  // expand all the items that need it in a single sweep
3814 
3815  // FIXME: This takes quite a lot of time, it could be made an interruptible job
3816 
3817  QList< Item * > * rootChildItems = mRootItem->childItems();
3818  if ( rootChildItems )
3819  {
3820  QList< Item * >::ConstIterator end( rootChildItems->constEnd() );
3821  for ( QList< Item * >::ConstIterator it = rootChildItems->constBegin(); it != end ;++it )
3822  {
3823  if ( ( *it )->initialExpandStatus() == Item::ExpandNeeded )
3824  syncExpandedStateOfSubtree( *it );
3825  }
3826  }
3827 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3828  Stats::expandingTreeTime = expandingTime.elapsed();
3829 #endif
3830  } else {
3831  mView->ignoreUpdateGeometries( false );
3832  // explicit call to updateGeometries() here
3833  mView->updateGeometries();
3834  }
3835 
3836  // this job has been completed
3837  delete mViewItemJobs.takeFirst();
3838 
3839 #ifdef KDEPIM_FOLDEROPEN_PROFILE
3840  // Last job finished!
3841  Stats::totalTime[currentPass] = Stats::currentJobStartTime.elapsed();
3842  printStatistics();
3843 #endif
3844 
3845  // take care of small jobs which never timeout by themselves because
3846  // of a small number of messages. At the end of each job check
3847  // the time used and if we're timeoutting and there is another job
3848  // then interrupt.
3849  elapsed = tStart.msecsTo( QTime::currentTime() );
3850  if ( ( elapsed > mViewItemJobStepChunkTimeout ) || ( elapsed < 0 ) )
3851  {
3852  if ( !mViewItemJobs.isEmpty() )
3853  return ViewItemJobInterrupted;
3854  // else it's completed in fact
3855  } // else proceed with the next job
3856 
3857  break;
3858  default:
3859  // This is *really* a BUG
3860  kWarning() << "ERROR: returned an invalid result";
3861  Q_ASSERT( false );
3862  break;
3863  }
3864  }
3865 
3866  // no more jobs
3867 
3868  emit q->statusMessage( i18nc( "@info:status Finished view fill", "Ready" ) );
3869 
3870  return ViewItemJobCompleted;
3871 }
3872 
3873 
3874 void ModelPrivate::viewItemJobStep()
3875 {
3876  // A single step in the View Fill operation.
3877  // This function wraps viewItemJobStepInternal() which does the step job
3878  // and either completes it or stops because of a timeout.
3879  // If the job is stopped then we start a zero-msecs timer to call us
3880  // back and resume the job. Otherwise we're just done.
3881 
3882  mViewItemJobStepStartTime = ::time( 0 );
3883 
3884  if( mFillStepTimer.isActive() )
3885  mFillStepTimer.stop();
3886 
3887  if ( !mStorageModel )
3888  return; // nothing more to do
3889 
3890 
3891  // Save the current item in the view as our process may
3892  // cause items to be reparented (and QTreeView will forget the current item in the meantime).
3893  // This machinery is also needed when we're about to remove items from the view in
3894  // a cleanup job: we'll be trying to set as current the item after the one removed.
3895 
3896  QModelIndex currentIndexBeforeStep = mView->currentIndex();
3897  Item * currentItemBeforeStep = currentIndexBeforeStep.isValid() ?
3898  static_cast< Item * >( currentIndexBeforeStep.internalPointer() ) : 0;
3899 
3900  // mCurrentItemToRestoreAfterViewItemJobStep will be zeroed out if it's killed
3901  mCurrentItemToRestoreAfterViewItemJobStep = currentItemBeforeStep;
3902 
3903  // Save the current item position in the viewport as QTreeView fails to keep
3904  // the current item in the sample place when items are added or removed...
3905  QRect rectBeforeViewItemJobStep;
3906 
3907  const bool lockView = mView->isScrollingLocked();
3908 
3909  // This is generally SLOW AS HELL... (so we avoid it if we lock the view and thus don't need it)
3910  if ( mCurrentItemToRestoreAfterViewItemJobStep && ( !lockView ) )
3911  rectBeforeViewItemJobStep = mView->visualRect( currentIndexBeforeStep );
3912 
3913  // FIXME: If the current item is NOT in the view, preserve the position
3914  // of the top visible item. This will make the view move yet less.
3915 
3916  // Insulate the View from (very likely spurious) "currentChanged()" signals.
3917  mView->ignoreCurrentChanges( true );
3918 
3919  // And go to real work.
3920  switch( viewItemJobStepInternal() )
3921  {
3922  case ViewItemJobInterrupted:
3923  // Operation timed out, need to resume in a while
3924  if ( !mInLengthyJobBatch )
3925  {
3926  mInLengthyJobBatch = true;
3927  mView->modelJobBatchStarted();
3928  }
3929  mFillStepTimer.start( mViewItemJobStepIdleInterval ); // this is a single shot timer connected to viewItemJobStep()
3930  // and go dealing with current/selection out of the switch.
3931  break;
3932  case ViewItemJobCompleted:
3933  // done :)
3934 
3935  Q_ASSERT( mModelForItemFunctions ); // UI must be no (longer) disconnected in this state
3936 
3937  // Ask the view to remove the eventual busy indications
3938  if ( mInLengthyJobBatch )
3939  {
3940  mInLengthyJobBatch = false;
3941  mView->modelJobBatchTerminated();
3942  }
3943 
3944  if ( mLoading )
3945  {
3946  mLoading = false;
3947  mView->modelFinishedLoading();
3948  }
3949 
3950  // Apply pre-selection, if any
3951  if ( mPreSelectionMode != PreSelectNone )
3952  {
3953  mView->ignoreCurrentChanges( false );
3954 
3955  bool bSelectionDone = false;
3956 
3957  switch( mPreSelectionMode )
3958  {
3959  case PreSelectLastSelected:
3960  // fall down
3961  break;
3962  case PreSelectFirstUnreadCentered:
3963  bSelectionDone = mView->selectFirstMessageItem( MessageTypeUnreadOnly, true ); // center
3964  break;
3965  case PreSelectOldestCentered:
3966  mView->setCurrentMessageItem( mOldestItem, true /* center */ );
3967  bSelectionDone = true;
3968  break;
3969  case PreSelectNewestCentered:
3970  mView->setCurrentMessageItem( mNewestItem, true /* center */ );
3971  bSelectionDone = true;
3972  break;
3973  case PreSelectNone:
3974  // deal with selection below
3975  break;
3976  default:
3977  kWarning() << "ERROR: Unrecognized pre-selection mode " << (int)mPreSelectionMode;
3978  break;
3979  }
3980 
3981  if ( ( !bSelectionDone ) && ( mPreSelectionMode != PreSelectNone ) )
3982  {
3983  // fallback to last selected, if possible
3984  if ( mLastSelectedMessageInFolder ) // we found it in the loading process: select and jump out
3985  {
3986  mView->setCurrentMessageItem( mLastSelectedMessageInFolder );
3987  bSelectionDone = true;
3988  }
3989  }
3990 
3991  if ( bSelectionDone ) {
3992  mLastSelectedMessageInFolder = 0;
3993  mPreSelectionMode = PreSelectNone;
3994  return; // already taken care of current / selection
3995  }
3996  }
3997  // deal with current/selection out of the switch
3998 
3999  break;
4000  default:
4001  // This is *really* a BUG
4002  kWarning() << "ERROR: returned an invalid result";
4003  Q_ASSERT( false );
4004  break;
4005  }
4006 
4007  // Everything else here deals with the selection
4008 
4009  // If UI is disconnected then we don't have anything else to do here
4010  if ( !mModelForItemFunctions )
4011  {
4012  mView->ignoreCurrentChanges( false );
4013  return;
4014  }
4015 
4016  // Restore current/selection and/or scrollbar position
4017 
4018  if ( mCurrentItemToRestoreAfterViewItemJobStep )
4019  {
4020  bool stillIgnoringCurrentChanges = true;
4021 
4022  // If the assert below fails then the previously current item got detached
4023  // and didn't get reattached in the step: this should never happen.
4024  Q_ASSERT( mCurrentItemToRestoreAfterViewItemJobStep->isViewable() );
4025 
4026  // Check if the current item changed
4027  QModelIndex currentIndexAfterStep = mView->currentIndex();
4028  Item * currentAfterStep = currentIndexAfterStep.isValid() ?
4029  static_cast< Item * >( currentIndexAfterStep.internalPointer() ) : 0;
4030 
4031  if ( mCurrentItemToRestoreAfterViewItemJobStep != currentAfterStep )
4032  {
4033  // QTreeView lost the current item...
4034  if ( mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep )
4035  {
4036  // Some view job code expects us to actually *change* the current item.
4037  // This is done by the cleanup step which removes items and tries
4038  // to set as current the item *after* the removed one, if possible.
4039  // We need the view to handle the change though.
4040  stillIgnoringCurrentChanges = false;
4041  mView->ignoreCurrentChanges( false );
4042  } else {
4043  // we just have to restore the old current item. The code
4044  // outside shouldn't have noticed that we lost it (e.g. the message viewer
4045  // still should have the old message opened). So we don't need to
4046  // actually notify the view of the restored setting.
4047  }
4048  // Restore it
4049  kDebug() << "Gonna restore current here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4050  mView->setCurrentIndex( q->index( mCurrentItemToRestoreAfterViewItemJobStep, 0 ) );
4051  } else {
4052  // The item we're expected to set as current is already current
4053  if ( mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep )
4054  {
4055  // But we have changed it in the job step.
4056  // This means that: we have deleted the current item and chosen a
4057  // new candidate as current but Qt also has chosen it as candidate
4058  // and already made it current. The problem is that (as of Qt 4.4)
4059  // it probably didn't select it.
4060  if ( !mView->selectionModel()->hasSelection() )
4061  {
4062  stillIgnoringCurrentChanges = false;
4063  mView->ignoreCurrentChanges( false );
4064 
4065  kDebug() << "Gonna restore selection here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
4066 
4067  QItemSelection selection;
4068  selection.append( QItemSelectionRange( q->index( mCurrentItemToRestoreAfterViewItemJobStep, 0 ) ) );
4069  mView->selectionModel()->select( selection, QItemSelectionModel::Select | QItemSelectionModel::Rows );
4070  }
4071  }
4072  }
4073 
4074  // FIXME: If it was selected before the change, then re-select it (it may happen that it's not)
4075  if ( !lockView )
4076  {
4077  // we prefer to keep the currently selected item steady in the view
4078  QRect rectAfterViewItemJobStep = mView->visualRect( q->index( mCurrentItemToRestoreAfterViewItemJobStep, 0 ) );
4079  if ( rectBeforeViewItemJobStep.y() != rectAfterViewItemJobStep.y() )
4080  {
4081  // QTreeView lost its position...
4082  mView->verticalScrollBar()->setValue( mView->verticalScrollBar()->value() + rectAfterViewItemJobStep.y() - rectBeforeViewItemJobStep.y() );
4083  }
4084  }
4085 
4086  // and kill the insulation, if not yet done
4087  if ( stillIgnoringCurrentChanges )
4088  mView->ignoreCurrentChanges( false );
4089 
4090  return;
4091  }
4092 
4093  // Either there was no current item before, or it was lost in a cleanup step and another candidate for
4094  // current item couldn't be found (possibly empty view)
4095  mView->ignoreCurrentChanges( false );
4096 
4097  if ( currentItemBeforeStep )
4098  {
4099  // lost in a cleanup..
4100  // tell the view that we have a new current, this time with no insulation
4101  mView->slotSelectionChanged( QItemSelection(), QItemSelection() );
4102  }
4103 }
4104 
4105 void ModelPrivate::slotStorageModelRowsInserted( const QModelIndex &parent, int from, int to )
4106 {
4107  if ( parent.isValid() )
4108  return; // ugh... should never happen
4109 
4110  Q_ASSERT( from <= to );
4111 
4112  int count = ( to - from ) + 1;
4113 
4114  mInvariantRowMapper->modelRowsInserted( from, count );
4115 
4116  // look if no current job is in the middle
4117 
4118  int jobCount = mViewItemJobs.count();
4119 
4120  for ( int idx = 0; idx < jobCount; idx++ )
4121  {
4122  ViewItemJob * job = mViewItemJobs.at( idx );
4123 
4124  if ( job->currentPass() != ViewItemJob::Pass1Fill )
4125  {
4126  // The job is a cleanup or in a later pass: the storage has been already accessed
4127  // and the messages created... no need to care anymore: the invariant row mapper will do the job.
4128  continue;
4129  }
4130 
4131  if ( job->currentIndex() > job->endIndex() )
4132  {
4133  // The job finished the Pass1Fill but still waits for the pass indicator to be
4134  // changed. This is unlikely but still may happen if the job has been interrupted
4135  // and then a call to slotStorageModelRowsRemoved() caused it to be forcibly completed.
4136  continue;
4137  }
4138 
4139  //
4140  // The following cases are possible:
4141  //
4142  // from to
4143  // | | -> shift up job
4144  // from to
4145  // | | -> shift up job
4146  // from to
4147  // | | -> shift up job
4148  // from to
4149  // | | -> split job
4150  // from to
4151  // | | -> split job
4152  // from to
4153  // | | -> job unaffected
4154  //
4155  //
4156  // FOLDER
4157  // |-------------------------|---------|--------------|
4158  // 0 currentIndex endIndex count
4159  // +-- job --+
4160  //
4161 
4162  if ( from > job->endIndex() )
4163  {
4164  // The change is completely above the job, the job is not affected
4165  continue;
4166  }
4167 
4168  if( from > job->currentIndex() ) // and from <= job->endIndex()
4169  {
4170  // The change starts in the middle of the job in a way that it must be split in two.
4171  // The first part is unaffected by the shift and ranges from job->currentIndex() to from - 1.
4172  // The second part ranges from "from" to job->endIndex() that are now shifted up by count steps.
4173 
4174  // First add a new job for the second part.
4175  ViewItemJob * newJob = new ViewItemJob( from + count, job->endIndex() + count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount() );
4176 
4177  Q_ASSERT( newJob->currentIndex() <= newJob->endIndex() );
4178 
4179  idx++; // we can skip this job in the loop, it's already ok
4180  jobCount++; // and our range increases by one.
4181  mViewItemJobs.insert( idx, newJob );
4182 
4183  // Then limit the original job to the first part
4184  job->setEndIndex( from - 1 );
4185 
4186  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4187 
4188  continue;
4189  }
4190 
4191  // The change starts below (or exactly on the beginning of) the job.
4192  // The job must be shifted up.
4193  job->setCurrentIndex( job->currentIndex() + count );
4194  job->setEndIndex( job->endIndex() + count );
4195 
4196  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4197  }
4198 
4199  bool newJobNeeded = true;
4200 
4201  // Try to attach to an existing fill job, if any.
4202  // To enforce consistency we can attach only if the Fill job
4203  // is the last one in the list (might be eventually *also* the first,
4204  // and even being already processed but we must make sure that there
4205  // aren't jobs _after_ it).
4206  if ( jobCount > 0 )
4207  {
4208  ViewItemJob * job = mViewItemJobs.at( jobCount - 1 );
4209  if ( job->currentPass() == ViewItemJob::Pass1Fill )
4210  {
4211  if (
4212  // The job ends just before the added rows
4213  ( from == ( job->endIndex() + 1 ) ) &&
4214  // The job didn't reach the end of Pass1Fill yet
4215  ( job->currentIndex() <= job->endIndex() )
4216  )
4217  {
4218  // We can still attach this :)
4219  job->setEndIndex( to );
4220  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4221  newJobNeeded = false;
4222  }
4223  }
4224  }
4225 
4226  if ( newJobNeeded )
4227  {
4228  // FIXME: Should take timing options from aggregation here ?
4229  ViewItemJob * job = new ViewItemJob( from, to, 100, 50, 10 );
4230  mViewItemJobs.append( job );
4231  }
4232 
4233  if ( !mFillStepTimer.isActive() )
4234  mFillStepTimer.start( mViewItemJobStepIdleInterval );
4235 }
4236 
4237 void ModelPrivate::slotStorageModelRowsRemoved( const QModelIndex &parent, int from, int to )
4238 {
4239  // This is called when the underlying StorageModel emits the rowsRemoved signal.
4240 
4241  if ( parent.isValid() )
4242  return; // ugh... should never happen
4243 
4244  // look if no current job is in the middle
4245 
4246  Q_ASSERT( from <= to );
4247 
4248  const int count = ( to - from ) + 1;
4249 
4250  int jobCount = mViewItemJobs.count();
4251 
4252  if (mRootItem && from == 0 && count == mRootItem->childItemCount() && jobCount == 0) {
4253  clear();
4254  return;
4255  }
4256 
4257  for ( int idx = 0; idx < jobCount; idx++ )
4258  {
4259  ViewItemJob * job = mViewItemJobs.at( idx );
4260 
4261  if ( job->currentPass() != ViewItemJob::Pass1Fill )
4262  {
4263  // The job is a cleanup or in a later pass: the storage has been already accessed
4264  // and the messages created... no need to care: we will invalidate the messages in a while.
4265  continue;
4266  }
4267 
4268  if ( job->currentIndex() > job->endIndex() )
4269  {
4270  // The job finished the Pass1Fill but still waits for the pass indicator to be
4271  // changed. This is unlikely but still may happen if the job has been interrupted
4272  // and then a call to slotStorageModelRowsRemoved() caused it to be forcibly completed.
4273  continue;
4274  }
4275 
4276  //
4277  // The following cases are possible:
4278  //
4279  // from to
4280  // | | -> shift down job
4281  // from to
4282  // | | -> shift down and crop job
4283  // from to
4284  // | | -> kill job
4285  // from to
4286  // | | -> split job, crop and shift
4287  // from to
4288  // | | -> crop job
4289  // from to
4290  // | | -> job unaffected
4291  //
4292  //
4293  // FOLDER
4294  // |-------------------------|---------|--------------|
4295  // 0 currentIndex endIndex count
4296  // +-- job --+
4297  //
4298 
4299  if ( from > job->endIndex() )
4300  {
4301  // The change is completely above the job, the job is not affected
4302  continue;
4303  }
4304 
4305  if( from > job->currentIndex() ) // and from <= job->endIndex()
4306  {
4307  // The change starts in the middle of the job and ends in the middle or after the job.
4308  // The first part is unaffected by the shift and ranges from job->currentIndex() to from - 1
4309  // We use the existing job for this.
4310  job->setEndIndex( from - 1 ); // stop before the first removed row
4311 
4312  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4313 
4314  if ( to < job->endIndex() )
4315  {
4316  // The change ends inside the job and a part of it can be completed.
4317 
4318  // We create a new job for the shifted remaining part. It would actually
4319  // range from to + 1 up to job->endIndex(), but we need to shift it down by count.
4320  // since count = ( to - from ) + 1 so from = to + 1 - count
4321 
4322  ViewItemJob * newJob = new ViewItemJob( from, job->endIndex() - count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount() );
4323 
4324  Q_ASSERT( newJob->currentIndex() < newJob->endIndex() );
4325 
4326  idx++; // we can skip this job in the loop, it's already ok
4327  jobCount++; // and our range increases by one.
4328  mViewItemJobs.insert( idx, newJob );
4329  } // else the change includes completely the end of the job and no other part of it can be completed.
4330 
4331  continue;
4332  }
4333 
4334  // The change starts below (or exactly on the beginning of) the job. ( from <= job->currentIndex() )
4335  if ( to >= job->endIndex() )
4336  {
4337  // The change completely covers the job: kill it
4338 
4339  // We don't delete the job since we want the other passes to be completed
4340  // This is because the Pass1Fill may have already filled mUnassignedMessageListForPass2
4341  // and may have set mOldestItem and mNewestItem. We *COULD* clear the unassigned
4342  // message list with clearUnassignedMessageLists() but mOldestItem and mNewestItem
4343  // could be still dangling pointers. So we just move the current index of the job
4344  // after the end (so storage model scan terminates) and let it complete spontaneously.
4345  job->setCurrentIndex( job->endIndex() + 1 );
4346 
4347  continue;
4348  }
4349 
4350  if ( to >= job->currentIndex() )
4351  {
4352  // The change partially covers the job. Only a part of it can be completed
4353  // and it must be shifted down. It would actually
4354  // range from to + 1 up to job->endIndex(), but we need to shift it down by count.
4355  // since count = ( to - from ) + 1 so from = to + 1 - count
4356  job->setCurrentIndex( from );
4357  job->setEndIndex( job->endIndex() - count );
4358 
4359  Q_ASSERT( job->currentIndex() <= job->endIndex() );
4360 
4361  continue;
4362  }
4363 
4364  // The change is completely below the job: it must be shifted down.
4365  job->setCurrentIndex( job->currentIndex() - count );
4366  job->setEndIndex( job->endIndex() - count );
4367  }
4368 
4369  // This will invalidate the ModelInvariantIndex-es that have been removed and return
4370  // them all in a nice list that we can feed to a view removal job.
4371  QList< ModelInvariantIndex * > * invalidatedIndexes = mInvariantRowMapper->modelRowsRemoved( from, count );
4372 
4373  if ( invalidatedIndexes )
4374  {
4375  // Try to attach to an existing cleanup job, if any.
4376  // To enforce consistency we can attach only if the Cleanup job
4377  // is the last one in the list (might be eventually *also* the first,
4378  // and even being already processed but we must make sure that there
4379  // aren't jobs _after_ it).
4380  if ( jobCount > 0 )
4381  {
4382  ViewItemJob * job = mViewItemJobs.at( jobCount - 1 );
4383  if ( job->currentPass() == ViewItemJob::Pass1Cleanup )
4384  {
4385  if ( ( job->currentIndex() <= job->endIndex() ) && job->invariantIndexList() )
4386  {
4387  //kDebug() << "Appending " << invalidatedIndexes->count() << " invalidated indexes to existing cleanup job" << endl;
4388  // We can still attach this :)
4389  *( job->invariantIndexList() ) += *invalidatedIndexes;
4390  job->setEndIndex( job->endIndex() + invalidatedIndexes->count() );
4391  delete invalidatedIndexes;
4392  invalidatedIndexes = 0;
4393  }
4394  }
4395  }
4396 
4397  if ( invalidatedIndexes )
4398  {
4399  // Didn't append to any existing cleanup job.. create a new one
4400 
4401  //kDebug() << "Creating new cleanup job for " << invalidatedIndexes->count() << " invalidated indexes" << endl;
4402  // FIXME: Should take timing options from aggregation here ?
4403  ViewItemJob * job = new ViewItemJob( ViewItemJob::Pass1Cleanup, invalidatedIndexes, 100, 50, 10 );
4404  mViewItemJobs.append( job );
4405  }
4406 
4407  if ( !mFillStepTimer.isActive() )
4408  mFillStepTimer.start( mViewItemJobStepIdleInterval );
4409  }
4410 }
4411 
4412 void ModelPrivate::slotStorageModelLayoutChanged()
4413 {
4414  kDebug() << "Storage model layout changed";
4415  // need to reset everything...
4416  q->setStorageModel( mStorageModel );
4417  kDebug() << "Storage model layout changed done";
4418 }
4419 
4420 void ModelPrivate::slotStorageModelDataChanged( const QModelIndex &fromIndex, const QModelIndex &toIndex )
4421 {
4422  Q_ASSERT( mStorageModel ); // must exist (and be the sender of the signal connected to this slot)
4423 
4424  int from = fromIndex.row();
4425  int to = toIndex.row();
4426 
4427  Q_ASSERT( from <= to );
4428 
4429  int count = ( to - from ) + 1;
4430 
4431  int jobCount = mViewItemJobs.count();
4432 
4433  // This will find out the ModelInvariantIndex-es that need an update and will return
4434  // them all in a nice list that we can feed to a view removal job.
4435  QList< ModelInvariantIndex * > * indexesThatNeedUpdate = mInvariantRowMapper->modelIndexRowRangeToModelInvariantIndexList( from, count );
4436 
4437  if ( indexesThatNeedUpdate )
4438  {
4439  // Try to attach to an existing update job, if any.
4440  // To enforce consistency we can attach only if the Update job
4441  // is the last one in the list (might be eventually *also* the first,
4442  // and even being already processed but we must make sure that there
4443  // aren't jobs _after_ it).
4444  if ( jobCount > 0 )
4445  {
4446  ViewItemJob * job = mViewItemJobs.at( jobCount - 1 );
4447  if ( job->currentPass() == ViewItemJob::Pass1Update )
4448  {
4449  if ( ( job->currentIndex() <= job->endIndex() ) && job->invariantIndexList() )
4450  {
4451  // We can still attach this :)
4452  *( job->invariantIndexList() ) += *indexesThatNeedUpdate;
4453  job->setEndIndex( job->endIndex() + indexesThatNeedUpdate->count() );
4454  delete indexesThatNeedUpdate;
4455  indexesThatNeedUpdate = 0;
4456  }
4457  }
4458  }
4459 
4460  if ( indexesThatNeedUpdate )
4461  {
4462  // Didn't append to any existing update job.. create a new one
4463  // FIXME: Should take timing options from aggregation here ?
4464  ViewItemJob * job = new ViewItemJob( ViewItemJob::Pass1Update, indexesThatNeedUpdate, 100, 50, 10 );
4465  mViewItemJobs.append( job );
4466  }
4467 
4468  if ( !mFillStepTimer.isActive() )
4469  mFillStepTimer.start( mViewItemJobStepIdleInterval );
4470  }
4471 
4472 }
4473 
4474 void ModelPrivate::slotStorageModelHeaderDataChanged( Qt::Orientation, int, int )
4475 {
4476  if ( mStorageModelContainsOutboundMessages!=mStorageModel->containsOutboundMessages() ) {
4477  mStorageModelContainsOutboundMessages = mStorageModel->containsOutboundMessages();
4478  emit q->headerDataChanged( Qt::Horizontal, 0, q->columnCount() );
4479  }
4480 }
4481 
4482 Qt::ItemFlags Model::flags( const QModelIndex &index ) const
4483 {
4484  if ( !index.isValid() )
4485  return Qt::NoItemFlags;
4486 
4487  Q_ASSERT( d->mModelForItemFunctions ); // UI must be connected if a valid index was queried
4488 
4489  Item * it = static_cast< Item * >( index.internalPointer() );
4490 
4491  Q_ASSERT( it );
4492 
4493  if ( it->type() == Item::GroupHeader )
4494  return Qt::ItemIsEnabled;
4495 
4496  Q_ASSERT( it->type() == Item::Message );
4497 
4498  if ( !static_cast< MessageItem * >( it )->isValid() )
4499  return Qt::NoItemFlags; // not enabled, not selectable
4500 
4501  if ( static_cast< MessageItem * >( it )->aboutToBeRemoved() )
4502  return Qt::NoItemFlags; // not enabled, not selectable
4503 
4504  if ( static_cast< MessageItem * >( it )->status().isDeleted() )
4505  return Qt::NoItemFlags; // not enabled, not selectable
4506 
4507  return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
4508 }
4509 
4510 QMimeData* MessageList::Core::Model::mimeData( const QModelIndexList& indexes ) const
4511 {
4512  QList< MessageItem* > msgs;
4513  foreach( const QModelIndex &idx, indexes ) {
4514  if( idx.isValid() ) {
4515  Item* item = static_cast< Item* >( idx.internalPointer() );
4516  if( item->type() == MessageList::Core::Item::Message ) {
4517  msgs << static_cast< MessageItem* >( idx.internalPointer() );
4518  }
4519  }
4520  }
4521  return storageModel()->mimeData( msgs );
4522 }
4523 
4524 
4525 Item *Model::rootItem() const
4526 {
4527  return d->mRootItem;
4528 }
4529 
4530 bool Model::isLoading() const
4531 {
4532  return d->mLoading;
4533 }
4534 
4535 MessageItem * Model::messageItemByStorageRow( int row ) const
4536 {
4537  if ( !d->mStorageModel )
4538  return 0;
4539  ModelInvariantIndex * idx = d->mInvariantRowMapper->modelIndexRowToModelInvariantIndex( row );
4540  if ( !idx )
4541  return 0;
4542 
4543  return static_cast< MessageItem * >( idx );
4544 }
4545 
4546 
4547 MessageItemSetReference Model::createPersistentSet( const QList< MessageItem * > &items )
4548 {
4549  if ( !d->mPersistentSetManager )
4550  d->mPersistentSetManager = new MessageItemSetManager();
4551 
4552  MessageItemSetReference ref = d->mPersistentSetManager->createSet();
4553  QList< MessageItem * >::ConstIterator end = items.constEnd();
4554  for ( QList< MessageItem * >::ConstIterator it = items.constBegin(); it != end; ++it )
4555  d->mPersistentSetManager->addMessageItem( ref, *it );
4556 
4557  return ref;
4558 }
4559 
4560 QList< MessageItem * > Model::persistentSetCurrentMessageItemList( MessageItemSetReference ref )
4561 {
4562  if ( d->mPersistentSetManager )
4563  return d->mPersistentSetManager->messageItems( ref );
4564  return QList< MessageItem * >();
4565 
4566 }
4567 
4568 void Model::deletePersistentSet( MessageItemSetReference ref )
4569 {
4570  if ( !d->mPersistentSetManager )
4571  return;
4572 
4573  d->mPersistentSetManager->removeSet( ref );
4574 
4575  if ( d->mPersistentSetManager->setCount() < 1 )
4576  {
4577  delete d->mPersistentSetManager;
4578  d->mPersistentSetManager = 0;
4579  }
4580 }
4581 
4582 #include "model.moc"
MessageList::Core::ModelPrivate::clearOrphanChildrenHash
void clearOrphanChildrenHash()
Definition: model.cpp:1054
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:327
MessageList::Core::Model::setPreSelectionMode
void setPreSelectionMode(PreSelectionMode preSelect)
Sets the pre-selection mode.
Definition: model.cpp:902
MessageList::Core::Aggregation::PerfectAndReferences
Thread by "In-Reply-To" and "References" fields.
Definition: aggregation.h:87
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:230
MessageList::Core::ModelPrivate::mThreadingCacheMessageInReplyToIdMD5ToMessageItem
QMultiHash< QByteArray, MessageItem * > mThreadingCacheMessageInReplyToIdMD5ToMessageItem
Threading cache.
Definition: model_p.h:219
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
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:267
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
MessageList::Core::View::messageItemBefore
Item * messageItemBefore(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
Finds message item that comes "before" the reference item.
Definition: view.cpp:1252
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:163
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:422
MessageList::Core::SortOrder::SortGroupsByDateTime
Sort groups by date/time of the group.
Definition: sortorder.h:54
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:317
MessageList::Core::ModelPrivate::mModelForItemFunctions
Model * mModelForItemFunctions
This pointer is passed to the Item functions that insert children.
Definition: model_p.h:415
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
MessageList::Core::MessageItem
Definition: messageitem.h:50
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:245
MessageList::Core::GroupHeaderItem::label
const QString & label() const
Definition: groupheaderitem.cpp:35
MessageList::Core::MessageItem::setThreadingStatus
void setThreadingStatus(ThreadingStatus threadingStatus)
Definition: messageitem.cpp:554
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:313
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:422
MessageList::Core::ModelPrivate::mViewItemJobs
QList< ViewItemJob * > mViewItemJobs
Pending fill view jobs, pointers are owned.
Definition: model_p.h:255
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:462
MessageList::Core::MessageItem::topmostMessage
MessageItem * topmostMessage()
Definition: messageitem.cpp:578
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:347
MessageList::Core::Theme::Column::pixmapName
const QString & pixmapName() const
Returns the icon's name (used in SmallIcon) set for this column.
Definition: theme.h:686
MessageList::Core::ModelPrivate::mFilter
const Filter * mFilter
The filter to apply on messages.
Definition: model_p.h:197
MessageList::Core::ModelPrivate::mOldestItem
MessageItem * mOldestItem
Definition: model_p.h:385
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
MessageList::Core::ModelPrivate::slotStorageModelLayoutChanged
void slotStorageModelLayoutChanged()
Definition: model.cpp:4412
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:337
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:4525
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
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:287
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:4535
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
MessageList::Core::Item::receiver
const QString & receiver() const
Returns the receiver associated to this item.
Definition: item.cpp:452
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:323
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:1987
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:182
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass5
ViewItemJobResult viewItemJobStepInternalForJobPass5(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2294
MessageList::Core::ItemMaxDateComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:301
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:472
MessageList::Core::Item::size
size_t size() const
Returns the size of this item (size of the Message, mainly)
Definition: item.cpp:412
MessageList::Core::View
The MessageList::View is the real display of the message list.
Definition: view.h:64
MessageList::Core::MessageItem::ParentMissing
this message might belong to a thread but its parent is actually missing
Definition: messageitem.h:80
MessageList::Core::ItemSenderComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:338
MessageList::Core::View::messageItemAfter
Item * messageItemAfter(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
Finds message item that comes "after" the reference item.
Definition: view.cpp:1122
MessageList::Core::ModelPrivate::mViewItemJobStepChunkTimeout
int mViewItemJobStepChunkTimeout
The timeout for a single ViewItemJob step.
Definition: model_p.h:283
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:699
MessageList::Core::MessageItem::ImperfectParentFound
this message found an imperfect parent to attach to (might be fixed later)
Definition: messageitem.h:79
MessageList::Core::ModelPrivate::mThreadingCacheMessageIdMD5ToMessageItem
QHash< QByteArray, MessageItem * > mThreadingCacheMessageIdMD5ToMessageItem
Threading cache.
Definition: model_p.h:213
MessageList::Core::Item::recomputeMaxDate
bool recomputeMaxDate()
Recompute the maximum date from the current children list.
Definition: item.cpp:276
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:249
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:395
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:1892
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:564
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:308
MessageList::Core::ModelPrivate::mView
View * mView
The view we're attacched to.
Definition: model_p.h:272
MessageList::Core::ItemSizeComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:259
MessageList::Core::ItemActionItemStatusComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:398
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:312
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:187
MessageList::Core::ModelPrivate::mViewItemJobStepIdleInterval
int mViewItemJobStepIdleInterval
The idle time between two ViewItemJob steps.
Definition: model_p.h:288
MessageList::Core::MessageItem::uniqueId
unsigned long uniqueId() const
Definition: messageitem.cpp:560
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:521
MessageList::Core::Item::type
Type type() const
Returns the type of this item.
Definition: item.cpp:298
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:240
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:207
MessageList::Core::MessageItem::NonThreadable
this message does not look as being threadable
Definition: messageitem.h:81
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:1662
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:566
MessageList::Core::View::ensureDisplayedWithParentsExpanded
void ensureDisplayedWithParentsExpanded(Item *it)
Makes sure that the specified is currently viewable by the user.
Definition: view.cpp:1844
messageitem.h
MessageList::Core::Item::firstChildItem
Item * firstChildItem() const
Returns the first child item, if any.
Definition: item.cpp:86
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:261
MessageList::Core::MessageItem::strippedSubjectMD5
QByteArray strippedSubjectMD5() const
Definition: messageitem.cpp:524
MessageList::Core::Model::mimeData
virtual QMimeData * mimeData(const QModelIndexList &indexes) const
Called when user initiates a drag from the messagelist.
Definition: model.cpp:4510
MessageList::Core::SortOrder::messageSorting
MessageSorting messageSorting() const
Returns the current message sorting option.
Definition: sortorder.h:126
MessageList::Core::ItemUnreadStatusComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:419
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:293
MessageList::Core::SortOrder::messageSortDirection
SortDirection messageSortDirection() const
Returns the current message SortDirection.
Definition: sortorder.h:138
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass1Update
ViewItemJobResult viewItemJobStepInternalForJobPass1Update(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:3185
MessageList::Core::Item::useReceiver
bool useReceiver() const
Returns whether sender or receiver is supposed to be displayed.
Definition: item.cpp:467
MessageList::Core::ItemSenderOrReceiverComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:378
MessageList::Core::MessageItem::messageIdMD5
QByteArray messageIdMD5() const
Definition: messageitem.cpp:476
MessageList::Core::ModelPrivate::mOrphanChildrenHash
QHash< MessageItem *, MessageItem * > mOrphanChildrenHash
Hash of orphan children used in Pass1Cleanup.
Definition: model_p.h:250
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:409
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:277
MessageList::Core::ModelPrivate::mThreadingCacheMessageSubjectMD5ToMessageItem
QHash< QByteArray, QList< MessageItem * > * > mThreadingCacheMessageSubjectMD5ToMessageItem
Threading cache.
Definition: model_p.h:225
MessageList::Core::ModelPrivate::mLoading
bool mLoading
Set to true in the first large loading job.
Definition: model_p.h:369
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:381
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
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:318
MessageList::Core::MessageItem::PerfectParentFound
this message found a perfect parent to attach to
Definition: messageitem.h:78
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:219
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:883
MessageList::Core::ModelPrivate::MaxDateChanged
Definition: model_p.h:116
MessageList::Core::Item::dump
void dump(const QString &prefix)
Debug helper.
Definition: item.cpp:543
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:2654
MessageList::Core::ModelPrivate::slotStorageModelRowsInserted
void slotStorageModelRowsInserted(const QModelIndex &parent, int from, int to)
Definition: model.cpp:4105
MessageList::Core::View::slotSelectionChanged
void slotSelectionChanged(const QItemSelection &current, const QItemSelection &)
Handles selection item management.
Definition: view.cpp:1921
MessageList::Core::Item::killAllChildItems
void killAllChildItems()
Kills all the child items without emitting any signal, recursively.
Definition: item.cpp:380
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:4474
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass1Cleanup
ViewItemJobResult viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2934
MessageList::Core::ModelPrivate::mViewItemJobStepStartTime
time_t mViewItemJobStepStartTime
The time at the current ViewItemJob step started.
Definition: model_p.h:278
MessageList::Core::ModelPrivate::slotStorageModelRowsRemoved
void slotStorageModelRowsRemoved(const QModelIndex &parent, int from, int to)
Definition: model.cpp:4237
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:397
MessageList::Core::Item::initialExpandStatus
InitialExpandStatus initialExpandStatus() const
The initial expand status we have to honor when attacching to the viewable root.
Definition: item.cpp:303
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass2
ViewItemJobResult viewItemJobStepInternalForJobPass2(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2544
manager.h
MessageList::Core::ModelPrivate::q
Model *const q
Definition: model_p.h:169
MessageList::Core::Item::GroupHeader
This item is a GroupHeaderItem.
Definition: item.h:63
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:4420
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:4568
QAbstractItemModel
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJobPass4
ViewItemJobResult viewItemJobStepInternalForJobPass4(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:2402
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
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:235
MessageList::Core::View::modelAboutToEmitLayoutChanged
void modelAboutToEmitLayoutChanged()
Definition: view.cpp:696
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:294
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:202
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:4547
MessageList::Core::ItemSubjectComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:318
MessageList::Core::ItemImportantStatusComparator
A helper class used with MessageList::Item::childItemNeedsReSorting() and MessageList::Item::insertCh...
Definition: item_p.h:443
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:4560
MessageList::Core::SortOrder::SortGroupsBySender
Sort groups by sender (makes sense only with GroupBySender)
Definition: sortorder.h:57
MessageList::Core::MessageItem::threadingStatus
ThreadingStatus threadingStatus() const
Definition: messageitem.cpp:548
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:358
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
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:702
MessageList::Core::SortOrder::NoMessageSorting
Don't sort the messages at all.
Definition: sortorder.h:80
MessageList::Core::ModelPrivate::viewItemJobStep
void viewItemJobStep()
Definition: model.cpp:3874
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:353
MessageList::Core::ModelPrivate::mRecursionCounterForReset
int mRecursionCounterForReset
counter to avoid infinite recursions in the setStorageModel() function
Definition: model_p.h:172
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
MessageList::Core::MessageItem::subjectIsPrefixed
bool subjectIsPrefixed() const
Definition: messageitem.cpp:518
MessageList::Core::MessageItem::accessibleText
QString accessibleText(const MessageList::Core::Theme *theme, int columnIndex)
Definition: messageitem.cpp:614
MessageList::Core::ModelPrivate::ViewItemJobResult
ViewItemJobResult
Definition: model_p.h:69
MessageList::Core::PreSelectNone
Definition: enums.h:46
MessageList::Core::ModelPrivate::addMessageToSubjectBasedThreadingCache
void addMessageToSubjectBasedThreadingCache(MessageItem *mi)
Definition: model.cpp:1540
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:432
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:61
MessageList::Core::SortOrder::Ascending
Definition: sortorder.h:69
MessageList::Core::ModelPrivate::viewItemJobStepInternalForJob
ViewItemJobResult viewItemJobStepInternalForJob(ViewItemJob *job, const QTime &tStart)
Definition: model.cpp:3333
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:399
MessageList::Core::Model::sortOrder
const SortOrder * sortOrder() const
Returns the sort order.
Definition: model.cpp:346
MessageList::Core::SortOrder::SortMessagesByImportantStatus
Sort the messages By "Important" flags of status.
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:946
MessageList::Core::SortOrder::groupSorting
GroupSorting groupSorting() const
Returns the GroupSorting.
Definition: sortorder.h:99
MessageList::Core::ModelPrivate::mSortOrder
const SortOrder * mSortOrder
The currently used sort order.
Definition: model_p.h:192
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:322
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:2729
MessageList::Core::Item::sender
const QString & sender() const
Returns the sender associated to this item.
Definition: item.cpp:442
MessageList::Core::Filter
This class is responsable of matching messages that should be displayed in the View.
Definition: filter.h:51
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
MessageList::Core::Item::setMaxDate
void setMaxDate(time_t date)
Sets the maximum date in the subtree originating from this item.
Definition: item.cpp:437
MessageList::Core::ModelPrivate::mInvariantRowMapper
ModelInvariantRowMapper * mInvariantRowMapper
Our mighty ModelInvariantRowMapper: used to workaround an issue related to the Model/View architectur...
Definition: model_p.h:302
MessageList::Core::Model::flags
virtual Qt::ItemFlags flags(const QModelIndex &index) const
Definition: model.cpp:4482
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:332
MessageList::Core::ModelPrivate::removeMessageFromSubjectBasedThreadingCache
void removeMessageFromSubjectBasedThreadingCache(MessageItem *mi)
Definition: model.cpp:1570
MessageList::Core::Theme::Column::label
const QString & label() const
Returns the label set for this column.
Definition: theme.h:674
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
MessageList::Core::ModelPrivate::mStorageModel
StorageModel * mStorageModel
The currently set storage model: shallow pointer.
Definition: model_p.h:177
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...
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:3666
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:402
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:281
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:2451
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:559
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:342
MessageList::Core::ModelPrivate::clearJobList
void clearJobList()
Definition: model.cpp:1066
MessageList::Core::ModelPrivate::mNewestItem
MessageItem * mNewestItem
Definition: model_p.h:386
MessageList::Core::Aggregation::BatchNoInteractivity
Do one large chunk, no interactivity at all.
Definition: aggregation.h:128
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:392
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:361
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:1610
MessageList::Core::ModelPrivate::mAggregation
const Aggregation * mAggregation
The currently set aggregation mode: shallow pointer set by Widget.
Definition: model_p.h:182
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)
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:500
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:482
MessageList::Core::SortOrder::SortGroupsByDateTimeOfMostRecent
Sort groups by date/time of the most recent message.
Definition: sortorder.h:55
MessageList::Core::MessageItem::inReplyToIdMD5
QByteArray inReplyToIdMD5() const
Definition: messageitem.cpp:488
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:4530
MessageList::Core::View::ignoreUpdateGeometries
void ignoreUpdateGeometries(bool ignore)
Used to enable/disable the ignoring of updateGeometries() calls.
Definition: view.cpp:177
MessageList::Core::StorageModel::PerfectThreadingOnly
Only the data for messageIdMD5 and inReplyToMD5 is needed.
Definition: storagemodelbase.h:97
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:307
MessageList::Core::Theme::columns
const QList< Column * > & columns() const
Returns the list of columns available in this theme.
Definition: theme.h:940
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
MessageList::Core::Item::setStatus
void setStatus(const Akonadi::MessageStatus &status)
Sets the status associated to this Item.
Definition: item.cpp:407
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:1942
MessageList::Core::Model::parent
virtual QModelIndex parent(const QModelIndex &index) const
Definition: model.cpp:574
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:55:32 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

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