[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kmail-devel
Subject:    Re: Reparenting on add/remove of messages
From:       Till Adam <till () adam-lilienthal ! de>
Date:       2003-03-28 13:01:31
[Download RAW message or body]

Members of the academy,

On Thursday 27 March 2003 10:01, Till Adam wrote:
> On Thursday 27 March 2003 09:47, Don Sanders wrote:

> > Hmm, I am getting a crash here. In my case when threading is turned on
> > for my 'search' folder (created when a search is performed) and I
> > perform a new search when the search folder is selected.
> >
> > I guess this is similar to collecting mail and receiving new mail in
> > the current folder with threading turned on.

[snip]

> Ok, I'll have time to work on this more today, hopefully. Thanks for
> trying.

Please find attached the next iteration of my attempts to get threading right 
when messages are added or removed from the folder. I've hopefully ironed out 
the remaining brain farts, so hopefully this one won't crash and burn like 
its predecessors. I hope I haven't scared people off trying this with those 
less than optimal earlier versions.

Cheerio, 

Till
["threadingAddedRemovedMsgs5.diff" (text/x-diff)]

Index: kmcommands.cpp
===================================================================
RCS file: /home/kde/kdepim/kmail/kmcommands.cpp,v
retrieving revision 1.33
diff -u -3 -p -w -r1.33 kmcommands.cpp
--- kmcommands.cpp	20 Mar 2003 01:18:39 -0000	1.33
+++ kmcommands.cpp	28 Mar 2003 12:48:00 -0000
@@ -1155,11 +1155,11 @@ void KMMoveCommand::execute()
   if (mDestFolder && mDestFolder->open() != 0)
     return;
   kernel->kbp()->busy();
-
-  KMMsgBase *curMsg = 0;
+  // used for remembering the message to select afterwards
+  int nextId;
   int contentX, contentY;
   if (mHeaders)
-    mHeaders->prepareMove( &curMsg, &contentX, &contentY );
+    mHeaders->prepareMove( &nextId, &contentX, &contentY );
 
   KMMessage *msg;
   KMMsgBase *msgBase;
@@ -1201,6 +1201,9 @@ void KMMoveCommand::execute()
         delete msg;
       }
     }
+    // adjust the remembered number, numbering is changed during delete.
+    if ( nextId > idx ) nextId--;
+
   }
   if (!list.isEmpty() && mDestFolder)
     mDestFolder->moveMsg(list, &index);
@@ -1212,7 +1215,7 @@ void KMMoveCommand::execute()
   }
 
   if (mHeaders)
-    mHeaders->finalizeMove( curMsg, contentX, contentY );
+    mHeaders->finalizeMove( nextId, contentX, contentY );
 
   if (mDestFolder) {
      mDestFolder->sync();
Index: kmfolder.cpp
===================================================================
RCS file: /home/kde/kdepim/kmail/kmfolder.cpp,v
retrieving revision 1.253
diff -u -3 -p -w -r1.253 kmfolder.cpp
--- kmfolder.cpp	23 Mar 2003 21:23:44 -0000	1.253
+++ kmfolder.cpp	28 Mar 2003 12:48:01 -0000
@@ -399,7 +399,7 @@ void KMFolder::removeMsg(int idx, bool)
   }
 
   KMMsgBase* mb = getMsgBase(idx);
-  QString msgIdMD5 = mb->msgIdMD5();
+
   Q_UINT32 serNum = kernel->msgDict()->getMsgSerNum(this, idx);
   if (!mQuiet && serNum != 0)
     emit msgRemoved(this, serNum);
@@ -417,7 +417,13 @@ void KMFolder::removeMsg(int idx, bool)
   --mTotalMsgs;
 
   if (!mQuiet) {
-    emit msgRemoved(idx, msgIdMD5);
+    QString msgIdMD5 = mb->msgIdMD5();
+    QString strippedSubjMD5 = mb->strippedSubjectMD5();
+    if (strippedSubjMD5.isEmpty()) {
+       mb->initStrippedSubjectMD5();
+       strippedSubjMD5 = mb->strippedSubjectMD5();
+    }
+    emit msgRemoved(idx, msgIdMD5, strippedSubjMD5);
     emit msgRemoved(this);
   } else {
     mChanged = TRUE;
@@ -441,7 +447,7 @@ KMMessage* KMFolder::take(int idx)
     emit msgRemoved(this,serNum);
 
   msg = (KMMessage*)takeIndexEntry(idx);
-  QString msgIdMD5 = msg->msgIdMD5();
+
   if (msg->status()==KMMsgStatusUnread ||
       msg->status()==KMMsgStatusNew ||
       (this == kernel->outboxFolder())) {
@@ -453,7 +459,13 @@ KMMessage* KMFolder::take(int idx)
   setDirty( true );
   needsCompact=true; // message is taken from here - needs to be compacted
   if (!mQuiet) {
-    emit msgRemoved(idx, msgIdMD5);
+    QString msgIdMD5 = msg->msgIdMD5();
+    QString strippedSubjMD5 = msg->strippedSubjectMD5();
+    if (strippedSubjMD5.isEmpty()) {
+       msg->initStrippedSubjectMD5();
+       strippedSubjMD5 = msg->strippedSubjectMD5();
+    }
+    emit msgRemoved(idx, msgIdMD5, strippedSubjMD5);
     emit msgRemoved(this);
   } else {
     mChanged = TRUE;
Index: kmfolder.h
===================================================================
RCS file: /home/kde/kdepim/kmail/kmfolder.h,v
retrieving revision 1.111
diff -u -3 -p -w -r1.111 kmfolder.h
--- kmfolder.h	20 Mar 2003 01:48:23 -0000	1.111
+++ kmfolder.h	28 Mar 2003 12:48:01 -0000
@@ -448,7 +448,7 @@ signals:
   void msgRemoved(KMFolder*, Q_UINT32 sernum);
 
   /** Emitted after a message is removed from the folder. */
-  void msgRemoved(int idx,QString msgIdMD5);
+  void msgRemoved(int idx,QString msgIdMD5, QString strippedSubjMD5);
   void msgRemoved(KMFolder*);
 
   /** Emitted when a message is added from the folder. */
Index: kmfoldermbox.cpp
===================================================================
RCS file: /home/kde/kdepim/kmail/kmfoldermbox.cpp,v
retrieving revision 1.60
diff -u -3 -p -w -r1.60 kmfoldermbox.cpp
--- kmfoldermbox.cpp	16 Mar 2003 03:52:42 -0000	1.60
+++ kmfoldermbox.cpp	28 Mar 2003 12:48:01 -0000
@@ -916,9 +916,9 @@ if( fileD1.open( IO_WriteOnly ) ) {
     return error;
   }
 
-  if (msgParent) {
+  if (msgParent) 
     if (idx >= 0) msgParent->take(idx);
-  }
+
 //  if (mAccount) aMsg->removeHeaderField("X-UID");
 
   if (aMsg->status()==KMMsgStatusUnread ||
Index: kmgroupware.cpp
===================================================================
RCS file: /home/kde/kdepim/kmail/kmgroupware.cpp,v
retrieving revision 1.11
diff -u -3 -p -w -r1.11 kmgroupware.cpp
--- kmgroupware.cpp	4 Mar 2003 03:18:02 -0000	1.11
+++ kmgroupware.cpp	28 Mar 2003 12:48:04 -0000
@@ -309,7 +309,7 @@ void KMGroupware::initFolders()
 	     this, SLOT( slotContactsFolderChanged() ) );
     connect( mContacts, SIGNAL( msgAdded(int) ),
 	     this, SLOT( slotContactsFolderChanged() ) );
-    connect( mContacts, SIGNAL( msgRemoved(int, QString) ),
+    connect( mContacts, SIGNAL( msgRemoved(int, QString, QString) ),
 	     this, SLOT( slotContactsFolderChanged() ) );
 
     node = mFolderParent->hasNamedFolder( folderName( KFolderTreeItem::Calendar ) );
@@ -326,7 +326,7 @@ void KMGroupware::initFolders()
 	     this, SLOT( slotCalendarFolderChanged() ) );
     connect( mCalendar, SIGNAL( msgAdded(int) ),
 	     this, SLOT( slotCalendarFolderChanged() ) );
-//     connect( mCalendar, SIGNAL( msgRemoved(int, QString) ),
+//     connect( mCalendar, SIGNAL( msgRemoved(int, QString, QString) ),
 // 	     this, SLOT( slotCalendarFolderChanged() ) );
 
 
@@ -345,7 +345,7 @@ void KMGroupware::initFolders()
 	     this, SLOT( slotNotesFolderChanged() ) );
     connect( mNotes, SIGNAL( msgAdded(int) ),
 	     this, SLOT( slotNotesFolderChanged() ) );
-    connect( mNotes, SIGNAL( msgRemoved(int, QString) ),
+    connect( mNotes, SIGNAL( msgRemoved(int, QString, QString) ),
 	     this, SLOT( slotNotesFolderChanged() ) );
 
     node = mFolderParent->hasNamedFolder( folderName( KFolderTreeItem::Tasks ) );
@@ -363,7 +363,7 @@ void KMGroupware::initFolders()
 	     this, SLOT( slotTasksFolderChanged() ) );
     connect( mTasks, SIGNAL( msgAdded(int) ),
 	     this, SLOT( slotTasksFolderChanged() ) );
-    connect( mTasks, SIGNAL( msgRemoved(int, QString) ),
+    connect( mTasks, SIGNAL( msgRemoved(int, QString, QString) ),
 	     this, SLOT( slotTasksFolderChanged() ) );
 
     // New interface:
Index: kmheaders.cpp
===================================================================
RCS file: /home/kde/kdepim/kmail/kmheaders.cpp,v
retrieving revision 1.487
diff -u -3 -p -w -r1.487 kmheaders.cpp
--- kmheaders.cpp	20 Mar 2003 01:50:35 -0000	1.487
+++ kmheaders.cpp	28 Mar 2003 12:48:04 -0000
@@ -827,8 +827,8 @@ void KMHeaders::setFolder (KMFolder *aFo
 		 this, SLOT(msgHeaderChanged(KMFolder*,int)));
       disconnect(mFolder, SIGNAL(msgAdded(int)),
 		 this, SLOT(msgAdded(int)));
-      disconnect(mFolder, SIGNAL(msgRemoved(int,QString)),
-		 this, SLOT(msgRemoved(int,QString)));
+      disconnect(mFolder, SIGNAL(msgRemoved(int,QString, QString)),
+		 this, SLOT(msgRemoved(int,QString, QString)));
       disconnect(mFolder, SIGNAL(changed()),
 		 this, SLOT(msgChanged()));
       disconnect(mFolder, SIGNAL(statusMsg(const QString&)),
@@ -853,8 +853,8 @@ void KMHeaders::setFolder (KMFolder *aFo
 	      this, SLOT(msgHeaderChanged(KMFolder*,int)));
       connect(mFolder, SIGNAL(msgAdded(int)),
 	      this, SLOT(msgAdded(int)));
-      connect(mFolder, SIGNAL(msgRemoved(int,QString)),
-	      this, SLOT(msgRemoved(int,QString)));
+      connect(mFolder, SIGNAL(msgRemoved(int,QString, QString)),
+	      this, SLOT(msgRemoved(int,QString, QString)));
       connect(mFolder, SIGNAL(changed()),
 	      this, SLOT(msgChanged()));
       connect(mFolder, SIGNAL(statusMsg(const QString&)),
@@ -999,6 +999,33 @@ void KMHeaders::msgChanged()
     emit selected(0);
 }
 
+//-----------------------------------------------------------------------------
+KMHeaderItem * KMHeaders::findParent(int id, bool *perfectParent)
+{
+    // This is the same logic as in readSortOrder, where it is also explained.
+    // TODO: merge the two
+    KMHeaderItem *parent = NULL;
+    QString replyToId = mFolder->getMsgBase(id)->replyToIdMD5();
+
+    if (!replyToId.isEmpty()) {
+        parent = mIdTree[replyToId];
+        if ( parent && perfectParent!=NULL )
+            *perfectParent = true;
+    }
+    if (!parent) {
+        // Try by replyToAuxId.
+        QString replyToAuxId = mFolder->getMsgBase(id)->replyToAuxIdMD5();
+        if (!replyToAuxId.isEmpty())
+            parent = mIdTree[replyToAuxId];
+    }
+    if (!parent && mFolder->getMsgBase(id)->subjectIsPrefixed()) {
+        // Still no parent, try by subject.
+        QString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
+        if (!subjMD5.isEmpty())
+            parent = mMsgSubjects[subjMD5];
+    }
+    return parent;
+}
 
 //-----------------------------------------------------------------------------
 void KMHeaders::msgAdded(int id)
@@ -1006,44 +1033,109 @@ void KMHeaders::msgAdded(int id)
   KMHeaderItem* hi = 0;
   if (!isUpdatesEnabled()) return;
 
-  mItems.resize( mFolder->count() );
+
   KMMsgBase* mb = mFolder->getMsgBase( id );
   assert(mb != 0); // otherwise using count() above is wrong
 
   if (mNested != mNestedOverride) {
-    QString msgId = mb->msgIdMD5();
+      QString msgId = mFolder->getMsgBase(id)->msgIdMD5();
     if (msgId.isNull())
       msgId = "";
-    QString replyToId = mb->replyToIdMD5();
+      QString replyToId = mFolder->getMsgBase(id)->replyToIdMD5();
 
-    if(mIdTree.isEmpty()) {
-      QString md5;
-      for(int x = 0; x < mFolder->count() - 1; x++) {
-	if(mItems[x]) {
-	  md5 = mFolder->getMsgBase(x)->msgIdMD5();
-	  if(md5.isEmpty()) continue;
-	  if(mIdTree[md5])
-	    ;
-	  else
-	    mIdTree.insert(md5, mItems[x]);
-	}
-      }
+      if(mIdTree.isEmpty())
+          buildIdTree(mFolder->count()-1);
+      else {
+          mIdTree.resize(mFolder->count());
+          mMsgSubjects.resize(mFolder->count());
     }
 
-    if (replyToId.isEmpty() || !mIdTree[replyToId])
+      bool perfectParent = false;
+      KMHeaderItem *parent = findParent(id, &perfectParent);
+      if (parent && !perfectParent) {
+          // The parent we found could be by subject, in which case it is
+          // possible, that it would be preferrable to thread it below us,
+          // not the other way around. Check that. This is not only
+          // cosmetic, as getting this wrong leads to circular threading.
+          if (msgId == mFolder->getMsgBase(parent->msgId())->replyToIdMD5()
+           || msgId == mFolder->getMsgBase(parent->msgId())->replyToAuxIdMD5())
+              parent = NULL;
+      }
+      if (parent)
+          hi = new KMHeaderItem( parent, id );
+      else
       hi = new KMHeaderItem( this, id );
+      
+      // Update and resize the id trees.
+      mItems.resize( mFolder->count() );
+      mItems[id] = hi;
+
+      mIdTree.resize(mFolder->count());
+      mIdTree.replace( msgId, hi );
+
+      mMsgSubjects.resize(mFolder->count());
+      QString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
+      if (subjMD5.isEmpty()) {
+          mFolder->getMsgBase(id)->initStrippedSubjectMD5();
+          subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
+      }
+      if( !subjMD5.isEmpty() && !mMsgSubjects.find(subjMD5) ) {
+          QString replyToIdMD5 = mFolder->getMsgBase(id)->replyToIdMD5();
+          QString replyToAuxIdMD5 = mFolder->getMsgBase(id)->replyToAuxIdMD5();
+          if ( (replyToIdMD5.isEmpty() || !mIdTree.find(replyToIdMD5))
+            && (replyToAuxIdMD5.isEmpty() || !mIdTree.find(replyToAuxIdMD5)))
+              mMsgSubjects.replace(subjMD5, hi );
+      }
+      // The message we just added might be a better parent for one of the as of
+      // yet imperfectly threaded messages. Let's find out.
+      for(QPtrListIterator<KMHeaderItem> it(mImperfectlyThreadedList); it.current(); ++it) {
+          int tryMe = (*it)->msgId();
+          // Check, whether our message is the replyToId or replyToAuxId of
+          // this one. If so, thread it below our message, unless it is already
+          // correctly threaded by replyToId.
+          bool perfectParent = true;
+          QString otherId = mFolder->getMsgBase(tryMe)->replyToIdMD5();
+          if (msgId != otherId) {
+              if (msgId != mFolder->getMsgBase(tryMe)->replyToAuxIdMD5())
+                  continue;
     else {
-      KMHeaderItem *parent = mIdTree[replyToId];
-      assert(parent);
-      hi = new KMHeaderItem( parent, id );
+                  if (!otherId.isEmpty() && mIdTree.find(otherId))
+                      continue;
+                  else
+                      // Thread below us by aux id, but keep on the list of
+                      // imperfectly threaded messages.
+                      perfectParent = false;
     }
-    if (!mIdTree[msgId])
-      mIdTree.replace( msgId, hi );
   }
+          QListViewItem *newParent = mItems[id];
+          QListViewItem *msg = mItems[tryMe];
+
+          if (msg->parent())
+              msg->parent()->takeItem(msg);
   else
-    hi = new KMHeaderItem( this, id );
+              takeItem(msg);
+          newParent->insertItem(msg);
+	  // Check if this message was a potential parent for threading by
+	  // subject. If so, replace it in the dict, we are better.
+	  QString mySubjMD5 = mFolder->getMsgBase(tryMe)->strippedSubjectMD5();
+	  if (mMsgSubjects[mySubjMD5] == msg )
+	      mMsgSubjects.replace(mySubjMD5, hi);
+
+          makeHeaderVisible();
 
+          if (perfectParent)
+              mImperfectlyThreadedList.remove ((*it));
+      }
+      // Add ourselves only now, to avoid circularity above.
+      if (hi && !perfectParent)
+          mImperfectlyThreadedList.append(hi);
+  } else { 
+      // non-threaded case
+      hi = new KMHeaderItem( this, id );
+      mItems.resize( mFolder->count() );
   mItems[id] = hi;
+  }
+
   appendUnsortedItem(hi); //inserted into sorted list
   if (mSortInfo.fakeSort) {
       QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
@@ -1064,22 +1156,43 @@ void KMHeaders::msgAdded(int id)
 
 
 //-----------------------------------------------------------------------------
-void KMHeaders::msgRemoved(int id, QString msgId)
+void KMHeaders::msgRemoved(int id, QString msgId, QString strippedSubjMD5)
 {
   if (!isUpdatesEnabled()) return;
 
   if ((id < 0) || (id >= (int)mItems.size()))
     return;
 
-  mIdTree.remove(msgId);
+  KMHeaderItem *removedItem = mItems[id];
+  for (int i = id; i < (int)mItems.size() - 1; ++i) {
+    mItems[i] = mItems[i+1];
+    mItems[i]->setMsgId( i );
+  }
+  mItems.resize( mItems.size() - 1 );
 
-  // Reparent children of item into top level
-  QListViewItem *myParent = mItems[id];
+  if (mNested != mNestedOverride) {
+      if (mIdTree.isEmpty())
+          buildIdTree(mFolder->count());
+      else {
+          mIdTree.remove(msgId);
+          // Remove the message from the list of potential parents for threading by
+          // subject. If we have a child, that one is the next best parent for
+          // threading by subject, so replace with that.
+          if (mMsgSubjects[strippedSubjMD5] == removedItem) {
+              mMsgSubjects.remove(strippedSubjMD5);
+              if (removedItem->firstChild())
+                  mMsgSubjects.replace(strippedSubjMD5,
+                          static_cast<KMHeaderItem*> (removedItem->firstChild()));
+          }
+          mIdTree.resize(mFolder->count());
+          mMsgSubjects.resize(mFolder->count());
+      }
+      // Reparent children of item.
+      QListViewItem *myParent = removedItem;
   QListViewItem *myChild = myParent->firstChild();
   QListViewItem *threadRoot = myParent;
   while (threadRoot->parent())
       threadRoot = threadRoot->parent();
-
   QString key = static_cast<KMHeaderItem*>(threadRoot)->key(mSortCol, !mSortDescending);
 
   while (myChild) {
@@ -1093,22 +1206,30 @@ void KMHeaders::msgRemoved(int id, QStri
       KMHeadersInherited::setSorting(mSortCol, !mSortDescending );
       mSortInfo.fakeSort = 0;
     }
+          bool perfectParent = false;
+          KMHeaderItem *parent = findParent(item->msgId(), &perfectParent);
+          if (parent && parent != item)
+              parent->insertItem(lastChild);
+          else
     insertItem(lastChild);
+
+          if (!parent || (!perfectParent && !mImperfectlyThreadedList.containsRef(item)))
+              mImperfectlyThreadedList.append(item);
+          if (parent && perfectParent && mImperfectlyThreadedList.containsRef(item))
+              mImperfectlyThreadedList.removeRef(item);
+      }
   }
 
-  if (currentItem() == mItems[id])
+  // Housekeeping.
+  if (currentItem() == removedItem)
     mPrevCurrent = 0;
-  KMHeaderItem *removedItem = mItems[id];
-  for (int i = id; i < (int)mItems.size() - 1; ++i) {
-    mItems[i] = mItems[i+1];
-    mItems[i]->setMsgId( i );
-  }
-  mItems.resize( mItems.size() - 1 );
+
   QListViewItem *next = removedItem->itemBelow();
   if (next && removedItem == static_cast<KMHeaderItem*>(currentItem()) ) {
     setCurrentItem( next );
     setSelected( next, TRUE );
   }
+  mImperfectlyThreadedList.removeRef(removedItem);
   delete removedItem;
 }
 
@@ -1380,7 +1501,7 @@ void KMHeaders::moveSelectedToFolder( in
 }
 
 //-----------------------------------------------------------------------------
-void KMHeaders::prepareMove( KMMsgBase **curMsg, int *contentX, int *contentY )
+void KMHeaders::prepareMove( int *id, int *contentX, int *contentY )
 {
   emit maybeDeleting();
 
@@ -1396,7 +1517,7 @@ void KMHeaders::prepareMove( KMMsgBase *
     curItem = curItem->itemAbove();
   item = static_cast<KMHeaderItem*>(curItem);
   if (item  && !item->isSelected())
-    *curMsg = mFolder->getMsgBase(item->msgId());
+    *id = item->msgId();
   *contentX = contentsX();
   *contentY = contentsY();
 
@@ -1411,21 +1532,20 @@ void KMHeaders::prepareMove( KMMsgBase *
   // So we block all signals for awhile to avoid this.
 
   blockSignals( true ); // don't emit signals when the current message is
-
 }
 
 //-----------------------------------------------------------------------------
-void KMHeaders::finalizeMove( KMMsgBase *curMsg, int contentX, int contentY )
+void KMHeaders::finalizeMove( int id, int contentX, int contentY )
 {
   blockSignals( false );
-
   emit selected( 0 );
-  if (curMsg) {
-    setSelected( currentItem(), TRUE );
-    setCurrentMsg( mFolder->find( curMsg ) );
+
+  if ( id > -1) {
+    setCurrentMsg( id );
     highlightMessage( currentItem(), false);
   }
 
+
   setContentsPos( contentX, contentY );
   makeHeaderVisible();
   connect( this, SIGNAL(currentChanged(QListViewItem*)),
@@ -1855,6 +1975,10 @@ void KMHeaders::updateMessageList(bool s
     repaint();
     return;
   }
+  if (mNested != mNestedOverride) {
+      mIdTree.clear();
+      mMsgSubjects.clear();
+  }
   readSortOrder(set_selection);
 }
 
@@ -2298,6 +2422,38 @@ static void internalWriteItem(FILE *sort
   }
 }
 
+void KMHeaders::buildIdTree (int count)
+{
+    mIdTree.resize(count);
+    mMsgSubjects.resize(count);
+
+    for(int x = 0; x < count; x++) {
+        QString md5;
+        if(!mItems[x])
+            continue;
+        md5 = mFolder->getMsgBase(x)->msgIdMD5();
+        if (!md5.isEmpty() && !mIdTree[md5])
+            mIdTree.insert(md5, mItems[x]);
+    }
+    for(int x = 0; x < count; x++) {
+        QString subjMD5;
+        if(!mItems[x])
+            continue;
+        subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
+        if (subjMD5.isEmpty()) {
+            mFolder->getMsgBase(x)->initStrippedSubjectMD5();
+            subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
+        }
+        if( !subjMD5.isEmpty() && !mMsgSubjects.find(subjMD5) ) {
+            QString replyToIdMD5 = mFolder->getMsgBase(x)->replyToIdMD5();
+            QString replyToAuxIdMD5 = mFolder->getMsgBase(x)->replyToAuxIdMD5();
+            if ( (replyToIdMD5.isEmpty() || !mIdTree.find(replyToIdMD5))
+              && (replyToAuxIdMD5.isEmpty() || !mIdTree.find(replyToAuxIdMD5)))
+                mMsgSubjects.replace(subjMD5, mItems[x]);
+        }
+    }
+}
+
 bool KMHeaders::writeSortOrder()
 {
   QString sortFile = KMAIL_SORT_FILE(mFolder);
@@ -2348,75 +2504,36 @@ bool KMHeaders::writeSortOrder()
       }
     }
 
-    if(mIdTree.isEmpty()) {
-      for(int x = 0; x < mFolder->count(); x++) {
-        QString md5;
-        if(mItems[x]) {
-          KMMsgBase *mb = mFolder->getMsgBase(x);
-          md5 = mb->msgIdMD5();
-          if (!md5.isEmpty() && !mIdTree[md5])
-            mIdTree.insert(md5, mItems[x]);
-        }
-      }
-    }
-
-    QDict<KMHeaderItem> msgSubjects(mFolder->count()*2);
-    for(int x = 0; x < mFolder->count(); x++) {
-      if(mItems[x]) {
-        QString subjMD5;
-        KMMsgBase *mb = mFolder->getMsgBase(x);
-        subjMD5 = mb->strippedSubjectMD5();
-        if (subjMD5.isEmpty()) {
-          mb->initStrippedSubjectMD5();
-          subjMD5 = mb->strippedSubjectMD5();
-        }
-        if( !subjMD5.isEmpty() && !msgSubjects.find(subjMD5) ) {
-          QString replyToIdMD5 = mb->replyToIdMD5();
-          QString replyToAuxIdMD5 = mb->replyToAuxIdMD5();
-          if ( (replyToIdMD5.isEmpty() || !mIdTree[replyToIdMD5])
-               && (replyToAuxIdMD5.isEmpty() || !mIdTree[replyToAuxIdMD5]) )
-            msgSubjects.insert(subjMD5, mItems[x]);
-        }
-      }
-    }
+    if(mIdTree.isEmpty())
+        buildIdTree(mFolder->count());
 
     KMMsgBase *kmb;
     while(KMHeaderItem *i = items.pop()) {
       kmb = mFolder->getMsgBase( i->mMsgId );
 
       QString replymd5 = kmb->replyToIdMD5();
+      QString replyToAuxId = kmb->replyToAuxIdMD5();
       int parent_id = -2; //no parent, top level
       KMHeaderItem *p = NULL;
       if(!replymd5.isEmpty())
         p = mIdTree[replymd5];
 
-      if (!p) {
-        // If we dont have a replyToId, or if we have one and the
-        // corresponding message is not in this folder, as happens
-        // if you keep your outgoing messages in an OUTBOX, for
-        // example, try the list of references, because the second
-        // to last will likely be in this folder. replyToAuxIdMD5 ontains
-        // the second to last one.
-        QString  ref = kmb->replyToAuxIdMD5();
-        if (!ref.isEmpty())
-          p = mIdTree[ref];
-      }
-      // still no parent, let's try by subject
-      // Force unprefixed subjects that would be threaded by subject
-      // to the top level.
-      if (!p && kmb->subjectIsPrefixed()) {
-        QString subjMD5 = kmb->strippedSubjectMD5();
-        if (!subjMD5.isEmpty()) {
-          p = msgSubjects[subjMD5];
-        }
-      }
-      if( p ) {
+      if (p)
         parent_id = p->mMsgId;
-        if (parent_id == i->mMsgId)
-          parent_id = -1;
-      }
       else
         parent_id = -1;
+      // We now have either found a parent, or set it to -1, which means that
+      // it will be reevaluated when a message is added, for example. If there
+      // is no replyToId and no replyToAuxId and the message is not prefixed,
+      // this message is top level, and will always be, so no need to
+      // reevaluate it.
+      if (replymd5.isEmpty()
+          && replyToAuxId.isEmpty()
+          && !kmb->subjectIsPrefixed() )
+              parent_id = -2;
+      // FIXME also mark messages with -1 as -2 a certain amount of time after
+      // their arrival, since it becomes very unlikely that a new parent for
+      // them will show up. (Ingo suggests a month.) -till
 
       internalWriteItem(sortStream, mFolder, i->mMsgId, parent_id,
 			i->key(mSortCol, !mSortDescending), FALSE);
@@ -2455,7 +2572,9 @@ void KMHeaders::appendUnsortedItem(KMHea
     int parent_id = -2; //no parent, top level
     if(khi->parent())
       parent_id = ((KMHeaderItem *)khi->parent())->mMsgId;
-    else if(!kmb->replyToIdMD5().isEmpty())
+    else if(!kmb->replyToIdMD5().isEmpty()
+        || !kmb->replyToAuxIdMD5().isEmpty()
+        || kmb->subjectIsPrefixed())
       parent_id = -1;
     internalWriteItem(sortStream, mFolder, khi->mMsgId, parent_id,
 		      khi->key(mSortCol, !mSortDescending));
@@ -2494,16 +2613,23 @@ class KMSortCacheItem {
     QPtrList<KMSortCacheItem> mSortedChildren;
     int mUnsortedCount, mUnsortedSize;
     KMSortCacheItem **mUnsortedChildren;
+    bool mImperfectlyThreaded;
 
 public:
     KMSortCacheItem() : mItem(0), mParent(0), mId(-1), mSortOffset(-1),
-	mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0) { }
+	mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0),
+        mImperfectlyThreaded (false) { }
     KMSortCacheItem(int i, QString k, int o=-1)
 	: mItem(0), mParent(0), mId(i), mSortOffset(o), mKey(k),
-	  mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0) { }
+	  mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0),
+          mImperfectlyThreaded (false) { }
     ~KMSortCacheItem() { if(mUnsortedChildren) free(mUnsortedChildren); }
 
     KMSortCacheItem *parent() const { return mParent; } //can't be set, only by the parent
+    bool isImperfectlyThreaded() const
+        { return mImperfectlyThreaded; }
+    void setImperfectlyThreaded (bool val)
+        { mImperfectlyThreaded = val; }
     bool hasChildren() const
 	{ return mSortedChildren.count() || mUnsortedCount; }
     const QPtrList<KMSortCacheItem> *sortedChildren() const
@@ -2586,9 +2712,6 @@ bool KMHeaders::readSortOrder(bool set_s
 
     //threaded cases
     QPtrList<KMSortCacheItem> unparented;
-    mIdTree.clear();
-    if (mIdTree.size() < 2*(unsigned)mFolder->count())
-	mIdTree.resize( 2*mFolder->count() );
 
     //cleanup
     noRepaint = TRUE;
@@ -2701,6 +2824,9 @@ bool KMHeaders::readSortOrder(bool set_s
 		    if(parent == -1) {
 			unparented.append(item);
 			root.addUnsortedChild(item);
+                        // Abuse appended for signaling that messages need to
+                        // threaded.
+                        appended = 1;
 		    } else {
 			if( ! sortCache[parent] )
 			    sortCache[parent] = new KMSortCacheItem;
@@ -2771,7 +2897,8 @@ bool KMHeaders::readSortOrder(bool set_s
 	SHOW_TIMER(holes);
     }
 
-    //make sure we've placed everything in parent/child relationship
+    // Make sure we've placed everything in parent/child relationship. All 
+    // messages with a parent id of -1 in the sort file are reevaluated here.
     if (appended && threaded && !unparented.isEmpty()) {
 	CREATE_TIMER(reparent);
 	START_TIMER(reparent);
@@ -2820,8 +2947,10 @@ bool KMHeaders::readSortOrder(bool set_s
 		// to last will likely be in this folder. replyToAuxIdMD5
 		// contains the second to last one.
 		QString  ref = msg->replyToAuxIdMD5();
-		if (!ref.isEmpty())
+               if (!ref.isEmpty()) {
 		    parent = msgs[ref];
+                   (*it)->setImperfectlyThreaded(true);
+               }
 	    }
 	    if (!parent && msg->subjectIsPrefixed()) {
 	        // Still no parent. Let's try by subject, but only if the
@@ -2831,6 +2960,7 @@ bool KMHeaders::readSortOrder(bool set_s
 	        QString subjMD5 = msg->strippedSubjectMD5();
 	        if (!subjMD5.isEmpty()) {
 		    parent = msgSubjects[subjMD5];
+                    (*it)->setImperfectlyThreaded(true);
 	        }
 	    }
 	    // If we have a parent, make sure it's not ourselves
@@ -2891,10 +3021,19 @@ bool KMHeaders::readSortOrder(bool set_s
 	}
     } while(!s.isEmpty());
 
+    if (threaded) mImperfectlyThreadedList.clear();
     for(int x = 0; x < mFolder->count(); x++) {	    //cleanup
 	if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
 	    khi = new KMHeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
 	    sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
+            // by definition ;)
+            sortCache[x]->setImperfectlyThreaded(true);
+	}
+        // Add all imperfectly threaded items to a list, so they can be
+        // reevaluated when a new message arrives which might be a better parent.
+        // Important for messages arriving out of order.
+        if (threaded && sortCache[x]->isImperfectlyThreaded()) {
+            mImperfectlyThreadedList.append(sortCache[x]->item());
 	}
 	delete sortCache[x];
 	sortCache[x] = 0;
@@ -2966,6 +3105,7 @@ bool KMHeaders::readSortOrder(bool set_s
     }
     if(sortStream)
 	fclose(sortStream);
+
     return TRUE;
 }
 
Index: kmheaders.h
===================================================================
RCS file: /home/kde/kdepim/kmail/kmheaders.h,v
retrieving revision 1.120
diff -u -3 -p -w -r1.120 kmheaders.h
--- kmheaders.h	20 Mar 2003 01:50:35 -0000	1.120
+++ kmheaders.h	28 Mar 2003 12:48:05 -0000
@@ -68,8 +68,8 @@ public:
   virtual void undo();
   virtual bool canUndo() const;
   virtual void resendMsg();
-  virtual void prepareMove( KMMsgBase **curMsg, int *contentX, int *contentY );
-  virtual void finalizeMove( KMMsgBase *curMsg, int contentX, int contentY );
+  virtual void prepareMove( int *id, int *contentX, int *contentY );
+  virtual void finalizeMove( int id, int contentX, int contentY );
 
   /** If destination is 0 then the messages are deleted, otherwise
     they are moved to this folder. */
@@ -169,7 +169,7 @@ public slots:
   /** For when the message with the given message id has been added to a folder */
   void msgAdded(int);
   /** For when the message with the given id has been removed for a folder */
-  void msgRemoved(int,QString);
+  void msgRemoved(int, QString, QString);
   /** Make the next header visible scrolling if necessary */
   void nextMessage();
   /** Same as nextMessage() but don't clear the current selection */
@@ -284,13 +284,19 @@ private:
   /** Map messages ids into KMHeaderItems */
   QMemArray<KMHeaderItem*> mItems;
   QDict< KMHeaderItem > mIdTree;
+  QDict< KMHeaderItem> mMsgSubjects;
+  /** Build the tree if necessary */
+  void buildIdTree (int count);
   QDict< KMHeaderItem > mPhantomIdTree;
   QDict< QValueList< int > > mTree;
   QDict< bool > mTreeSeen;
   QDict< bool > mTreeToplevel;
   bool mNested, mNestedOverride;
   NestingPolicy nestingPolicy;
+  QPtrList<KMHeaderItem> mImperfectlyThreadedList;
 
+  /** Find a msg to thread mb below */
+  KMHeaderItem * findParent(int id, bool *perfectParent = NULL);
   /** These must replaced by something better! */
   static bool mTrue, mFalse;
 
Index: kmmainwidget.cpp
===================================================================
RCS file: /home/kde/kdepim/kmail/kmmainwidget.cpp,v
retrieving revision 1.38
diff -u -3 -p -w -r1.38 kmmainwidget.cpp
--- kmmainwidget.cpp	24 Mar 2003 13:01:46 -0000	1.38
+++ kmmainwidget.cpp	28 Mar 2003 12:48:06 -0000
@@ -635,7 +635,7 @@ void KMMainWidget::createWidgets(void)
   accel->connectItem(accel->insertItem(CTRL+Key_Space),
                      mFolderTree, SLOT(selectCurrentFolder()));
 
-  connect( kernel->outboxFolder(), SIGNAL( msgRemoved(int, QString) ),
+  connect( kernel->outboxFolder(), SIGNAL( msgRemoved(int, QString, QString) ),
            SLOT( startUpdateMessageActionsTimer() ) );
   connect( kernel->outboxFolder(), SIGNAL( msgAdded(int) ),
            SLOT( startUpdateMessageActionsTimer() ) );

_______________________________________________
KMail Developers mailing list
kmail@mail.kde.org
http://mail.kde.org/mailman/listinfo/kmail

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic