diff -Naur balsa-1.0.0/libbalsa/mailbox.c balsa-1.0.0-threading/libbalsa/mailbox.c --- balsa-1.0.0/libbalsa/mailbox.c Thu Nov 16 14:47:15 2000 +++ balsa-1.0.0-threading/libbalsa/mailbox.c Thu Nov 16 14:46:03 2000 @@ -777,6 +777,15 @@ SKIPWS(p); message->in_reply_to = g_strdup(p); + } else if (g_strncasecmp ("In-Reply-To:", tmp->data, 12) == 0){ + p = tmp->data + 12; + while(*p!='\0'&& *p!='<')p++; + if(*p!='\0'){ + message->in_reply_to = g_strdup (p); + p=message->in_reply_to; + while(*p!='\0' && *p!='>')p++; + if(*p=='>')*(p+1)='\0'; + } } tmp = tmp->next; } @@ -790,6 +799,34 @@ } /* more! */ + + if(cenv->references!=NULL){ + LIST* p=cenv->references; + while(p!=NULL){ + message->references_for_threading = + g_list_append(message->references_for_threading, + g_strdup(p->data)); + p=p->next; + } + message->references_for_threading=g_list_reverse(message->references_for_threading); + } + +#if 0 + /* According to RFC 1036 (section 2.2.5), MessageIDs in References header + * must be in the oldest first order; the direct parent should be last. + * It seems, however, some MUAs ignore this rule. + * For example, one of them adds the direct parent to the head of + * the References header. + */ + if(message->in_reply_to != NULL && + message->references_for_threading != NULL && + 1references_for_threading) && + strcmp(message->in_reply_to, + (g_list_first(message->references_for_threading))->data)==0){ + GList *foo=message->references_for_threading; + message->references_for_threading=g_list_remove(foo, foo->data); + } +#endif return message; } diff -Naur balsa-1.0.0/libbalsa/message.c balsa-1.0.0-threading/libbalsa/message.c --- balsa-1.0.0/libbalsa/message.c Thu Nov 16 14:47:15 2000 +++ balsa-1.0.0-threading/libbalsa/message.c Thu Nov 16 14:46:03 2000 @@ -116,6 +116,7 @@ message->message_id = NULL; message->body_ref = 0; message->body_list = NULL; + message->references_for_threading = NULL; } @@ -255,6 +256,16 @@ libbalsa_message_body_free(message->body_list); message->body_list = NULL; + + if(message->references_for_threading!=NULL){ + GList *list=message->references_for_threading; + for(; list; list=g_list_next(list)){ + if(list->data) + g_free(list->data); + } + g_list_free (message->references_for_threading); + message->references_for_threading=NULL; + } } const gchar * diff -Naur balsa-1.0.0/libbalsa/message.h balsa-1.0.0-threading/libbalsa/message.h --- balsa-1.0.0/libbalsa/message.h Thu Nov 16 14:47:15 2000 +++ balsa-1.0.0-threading/libbalsa/message.h Thu Nov 16 14:46:03 2000 @@ -83,6 +83,7 @@ /* replied message ID's */ GList *references; + GList *references_for_threading; /* oldest first */ /* replied message ID; from address on date */ gchar *in_reply_to; diff -Naur balsa-1.0.0/libmutt/parse.c balsa-1.0.0-threading/libmutt/parse.c --- balsa-1.0.0/libmutt/parse.c Thu Sep 21 22:12:02 2000 +++ balsa-1.0.0-threading/libmutt/parse.c Thu Nov 16 14:46:03 2000 @@ -892,7 +892,14 @@ long loc; int matched; size_t linelen = LONG_STRING; - + int force_user_hdrs; /* BALSA */ + /* This variable was added to push In-Reply-To: header + * to e->userhdrs, which will be parsed in + * libbalsa/mailbox.c + * It will be preferable to use user_hdrs in calling + * mutt_read_rfc822_header function in stead of + * using this variable. + */ in_reply_to[0] = 0; if (hdr) @@ -913,6 +920,7 @@ while (*(line = read_rfc822_line (f, line, &linelen)) != 0) { matched = 0; + force_user_hdrs = 0; /* BALSA */ if ((p = strpbrk (line, ": \t")) == NULL || *p != ':') { @@ -1047,6 +1055,7 @@ strfcpy (in_reply_to, p, sizeof (in_reply_to)); rfc2047_decode (in_reply_to, in_reply_to, sizeof (in_reply_to)); + force_user_hdrs = 1; /* BALSA */ } } break; @@ -1201,7 +1210,7 @@ } /* Keep track of the user-defined headers */ - if (!matched && user_hdrs) + if (!matched && (user_hdrs || force_user_hdrs)) /* BALSA */ { if (last) { diff -Naur balsa-1.0.0/src/Makefile.am balsa-1.0.0-threading/src/Makefile.am --- balsa-1.0.0/src/Makefile.am Thu Oct 12 03:06:37 2000 +++ balsa-1.0.0-threading/src/Makefile.am Thu Nov 16 14:46:03 2000 @@ -11,6 +11,8 @@ balsa-icons.h \ balsa-index.c \ balsa-index.h \ + balsa-index-threading.c \ + balsa-index-threading.h \ balsa-index-page.c \ balsa-index-page.h \ balsa-mblist.c \ diff -Naur balsa-1.0.0/src/balsa-index-page.c balsa-1.0.0-threading/src/balsa-index-page.c --- balsa-1.0.0/src/balsa-index-page.c Thu Nov 16 14:47:15 2000 +++ balsa-1.0.0-threading/src/balsa-index-page.c Thu Nov 16 14:46:03 2000 @@ -477,7 +477,7 @@ guint row, column; LibBalsaMessage *current_message; GtkCList *clist; - LibBalsaMailbox *mailbox; + /* LibBalsaMailbox *mailbox; */ clist = GTK_CLIST(widget); on_message = @@ -485,7 +485,7 @@ &column); if (on_message) { - mailbox = gtk_clist_get_row_data(clist, row); + /* mailbox = gtk_clist_get_row_data(clist, row); */ current_message = LIBBALSA_MESSAGE(gtk_clist_get_row_data(clist, row)); @@ -639,7 +639,7 @@ list = clist->selection; while (list) { message = - gtk_clist_get_row_data(clist, GPOINTER_TO_INT(list->data)); + gtk_ctree_node_get_row_data(GTK_CTREE(bindex), list->data); messages=g_list_append(messages, message); list = list->next; } @@ -719,7 +719,7 @@ guint32 time) { /*--*/ - guint *selected_rows; + GtkCTreeNode **selected_rows; guint nb_selected_rows; GtkCList *clist; @@ -742,8 +742,8 @@ for (message_count = 0; message_count < nb_selected_rows; message_count++) { current_message = - LIBBALSA_MESSAGE(gtk_clist_get_row_data - (clist, selected_rows[message_count])); + LIBBALSA_MESSAGE(gtk_ctree_node_get_row_data + (GTK_CTREE(widget), selected_rows[message_count])); message_list[message_count] = current_message; } @@ -772,8 +772,8 @@ list = GTK_CLIST(index)->selection; while (list) { message = - gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); sm = sendmsg_window_new(widget, message, SEND_REPLY); gtk_signal_connect(GTK_OBJECT(sm->window), "destroy", GTK_SIGNAL_FUNC(sendmsg_window_destroy_cb), @@ -795,8 +795,8 @@ list = GTK_CLIST(index)->selection; while (list) { message = - gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); sm = sendmsg_window_new(widget, message, SEND_REPLY_ALL); gtk_signal_connect(GTK_OBJECT(sm->window), "destroy", GTK_SIGNAL_FUNC(sendmsg_window_destroy_cb), @@ -818,8 +818,8 @@ list = GTK_CLIST(index)->selection; while (list) { message = - gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); sm = sendmsg_window_new(widget, message, SEND_FORWARD); gtk_signal_connect(GTK_OBJECT(sm->window), "destroy", GTK_SIGNAL_FUNC(sendmsg_window_destroy_cb), @@ -842,8 +842,8 @@ list = GTK_CLIST(index)->selection; while (list) { message = - gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); sm = sendmsg_window_new(widget, message, SEND_CONTINUE); gtk_signal_connect(GTK_OBJECT(sm->window), "destroy", GTK_SIGNAL_FUNC(sendmsg_window_destroy_cb), @@ -891,8 +891,8 @@ /* First see if we should unselect or select */ while (list) { - message = gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + message = gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); if (!(message->flags & LIBBALSA_MESSAGE_FLAG_FLAGGED)) { is_all_flagged = FALSE; @@ -905,8 +905,8 @@ list = GTK_CLIST(index)->selection; while (list) { - message = gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + message = gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); if (is_all_flagged) { libbalsa_message_unflag(message); @@ -939,8 +939,8 @@ to_trash = TRUE; while (list) { - message = gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + message = gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); messages=g_list_append(messages, message); list = list->next; } @@ -977,8 +977,8 @@ list = GTK_CLIST(index)->selection; while (list) { message = - gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT(list->data)); + gtk_ctree_node_get_row_data(GTK_CTREE(index), + list->data); libbalsa_message_undelete(message); list = list->next; } diff -Naur balsa-1.0.0/src/balsa-index-threading.c balsa-1.0.0-threading/src/balsa-index-threading.c --- balsa-1.0.0/src/balsa-index-threading.c Thu Jan 1 00:00:00 1970 +++ balsa-1.0.0-threading/src/balsa-index-threading.c Thu Nov 16 14:46:28 2000 @@ -0,0 +1,929 @@ +/* -*-mode:c; c-style:k&r; c-basic-offset:2; -*- */ +/* Balsa E-Mail Client + * Copyright (C) 1997-2000 Stuart Parmenter and others, + * See the file AUTHORS for a list. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/* + * This file includes two message threading functions. + * The first is the implementation of jwz's algorithm describled at + * http://www.jwz.org/doc/threading.html . The another is very simple and + * trivial one. If you confirm that your mailbox includes every threaded + * messages, the later will be enough. Those functions are selectable on + * each mailbox by setting the 'type' member in BalsaIndex. If you don't need + * message threading functionality, just specify 'BALSA_INDEX_THREADING_FLAT'. + * + * ymnk@jcraft.com + */ + +#include "config.h" + +#include +#include +#include +#include +#include "balsa-index.h" +#include "balsa-index-threading.h" + +#define MESSAGE(node) ((LibBalsaMessage *)(GTK_CTREE_ROW(node)->row.data)) + +static void threading_jwz(BalsaIndex* bindex, int type); +static void gen_container(GtkCTree *ctree, + GtkCTreeNode *node, + GHashTable* id_table); +static void find_root_set(gpointer key, GNode* value, GSList ** root_set); +static gboolean prune(GNode *node, GSList *root_set); +static gboolean node_equal(GNode *node1, GNode **node2); +static gboolean construct(GNode *node, GtkCTree *ctree); +static void subject_gather(GNode *node, GHashTable* subject_table); +static void subject_merge(GNode *node, GHashTable* subject_table, + GSList* root_set, GSList** save_node); +static void reparent(GNode* node, GNode* children); +static void free_node(gpointer key, GNode* value, gpointer data); +static gchar* chop_re(gchar* str); + +static void threading_simple(BalsaIndex* bindex); +static void add_message(GtkCTree *ctree, + GtkCTreeNode *node, + GHashTable* msg_table); + +static void dump(GNode *node, int indent); + +void +balsa_index_threading(BalsaIndex* bindex){ + int type=bindex->threading_type; + switch (type){ + case BALSA_INDEX_THREADING_SIMPLE: + threading_simple(bindex); + break; + case BALSA_INDEX_THREADING_JWZ: + threading_jwz(bindex, type); + break; + case BALSA_INDEX_THREADING_FLAT: + break; + default: + } +} + +static +void +threading_jwz(BalsaIndex* bindex, int type){ + GHashTable *id_table; + GHashTable *subject_table; + GSList *root_set=NULL; + GSList *save_node=NULL; + GSList *foo=NULL; + GtkCTree* ctree=GTK_CTREE(bindex); + + id_table=g_hash_table_new(g_str_hash, g_str_equal); + gtk_ctree_pre_recursive(ctree, + (GtkCTreeNode *)NULL, + (GtkCTreeFunc)gen_container, + (gpointer)id_table); + + g_hash_table_foreach(id_table, + (GHFunc)find_root_set, + &root_set); + + foo=root_set; + while(foo){ + g_node_traverse(foo->data, + /*G_PRE_ORDER,*/ + G_POST_ORDER, + G_TRAVERSE_ALL, + -1, + (GNodeTraverseFunc)prune, + root_set); + foo=g_slist_next(foo); + } + + subject_table=g_hash_table_new(g_str_hash, g_str_equal); + g_slist_foreach(root_set, (GFunc)subject_gather, subject_table); + + foo=root_set; + while(foo){ + if(foo->data!=NULL) + subject_merge(foo->data, subject_table, root_set, &save_node); + foo=g_slist_next(foo); + } + + foo=root_set; + while(foo){ + if(foo->data!=NULL) + g_node_traverse(foo->data, + G_PRE_ORDER, + G_TRAVERSE_ALL, + -1, + (GNodeTraverseFunc)construct, + ctree); + foo=g_slist_next(foo); + } + + foo=save_node; + while(foo){ + if(foo->data!=NULL){ + ((GNode*)(foo->data))->children=NULL; + g_node_destroy((GNode*)(foo->data)); + } + foo=g_slist_next(foo); + } + if(save_node!=NULL) + g_slist_free(save_node); + + g_hash_table_destroy(subject_table); + g_hash_table_foreach(id_table, (GHFunc)free_node, NULL); + g_hash_table_destroy(id_table); + + if(root_set!=NULL) + g_slist_free(root_set); +} + +static +void +gen_container(GtkCTree *ctree, + GtkCTreeNode *node, + GHashTable *id_table){ + LibBalsaMessage* message=MESSAGE(node); + + GNode* container=NULL; + GNode* parent=NULL; + + if(!message->message_id){ + return; + } + + /* + * If id_table contains a Container for this ID: + * + Store this message in the Container's message slot. + * else + * + Create a new Container object holding this message; + * + Index the Container by Message-ID in id_table. + */ + + container=g_hash_table_lookup(id_table, message->message_id); + + if(container!=NULL){ + container->data=message; + } + else{ + container=g_node_new(message); + g_hash_table_insert(id_table, message->message_id, container); + } + + /* + * For each element in the message's References field: + * + Find a Container object for the given Message-ID: + * + If there's one in id_table use that; + * + Otherwise, make (and index) one with a null Message. + * + Link the References field's Containers together in the order + * implied by the References header. + * + If they are already linked, don't change the existing links. + * + Do not add a link if adding that link would introduce a loop: + * that is, before asserting A->B, search down the children of B + * to see if A is reachable. + */ + + { + GList *reference=message->references_for_threading; + GNode* foo; + char *id; + while(reference){ + id=(char *)(reference->data); + foo=g_hash_table_lookup(id_table, id); + if(foo==NULL){ + foo=g_node_new(NULL); + g_hash_table_insert(id_table, id, foo); + } + + if(foo==parent){ + /* printf("duplicate!!\n"); */ + reference=g_list_next(reference); + continue; + } + + if(parent!=NULL){ + int find=0; + /* checking for cyclic tree */ +#if 0 + /* This case will be enough for jwz's rule */ + GNode*bar=foo; + while(bar){ + if(bar->children==parent){find=1; break;} + bar=bar->children; + } +#else + GNode *bar=parent; + g_node_traverse(foo, + G_PRE_ORDER, + G_TRAVERSE_ALL, + -1, + (GNodeTraverseFunc)node_equal, + &bar); + if(bar==NULL) find=1; +#endif + + if(find){ + /* hmm.... */ + /* printf("found 1#\n"); */ + } + else{ + /* + printf("parent->children=%x, foo->parent=%x\n", + (int)(parent->children), (int)(foo->parent)); + */ + + if(parent->children==NULL && foo->parent==NULL){ + parent->children=foo; + foo->parent=parent; + } + else{ + + if(foo->parent!=NULL){ + + if(parent->children!=NULL){ + if(foo->parent == parent){ + parent=foo; + reference=g_list_next(reference); + continue; + } + return; + } + + if(foo->children!=NULL){ + if(g_list_next(reference)!=NULL && + g_list_next(reference)->data!=NULL){ + GNode *bar=g_hash_table_lookup(id_table, + g_list_next(reference)->data); + if(bar==foo->children){ + parent=foo; + reference=g_list_next(reference); + continue; + } + } + } + return; + } + + if(parent->children!=NULL){ + /* + printf("parent->children=%x, foo=%x\n", + (int)(parent->children), (int)foo); + */ + +#if 1 + /* This part is not defined in jwz's algorithm. */ + { + GNode* p=parent->children; + while(p->next){ + if(p==foo){ break; } + p=p->next; + } + if(p!=foo){ + p->next=foo; + foo->prev=p; + } + + if(foo->next){ + p=foo->next; + while(p){ + p->parent=parent; + p=p->next; + } + } + + foo->parent=parent; + parent=foo; + reference=g_list_next(reference); + continue; + } +#else + /* printf("return 2#\n"); */ + return; +#endif + } + /* + printf("%x has already children(%x) and I'm %x, my parent is %x\n", + (int)parent,(int)(parent->children),(int)foo,(int)(foo->parent)); + */ + } + parent=foo; + } + } + else{ + parent=foo; + } + reference=g_list_next(reference); + } + } + + /* + * Set the parent of this message to be the last element in References. + * Note that this message may have a parent already: this can happen + * because we saw this ID in a References field, and presumed a + * parent based on the other entries in that field. Now that we have + * the actual message, we can be more definitive, so throw away the + * old parent and use this new one. Find this Container in the + * parent's children list, and unlink it. + * + * Note that this could cause this message to now have no parent, if + * it has no references field, but some message referred to it as the + * non-first element of its references. (Which would have been some + * kind of lie...) + * + * Note that at all times, the various ``parent'' and ``child'' + * fields must be kept inter-consistent. + */ + + if(parent!=NULL && parent->children==NULL){ + + /* checking for cyclic tree */ + { + int find=0; +#if 0 + /* This case will be enough for jwz's rule */ + GNode*bar=container; + while(bar){ + if(bar->children==parent){find=1; break;} + bar=bar->children; + } +#else + GNode *bar=parent; + g_node_traverse(container, + G_PRE_ORDER, + G_TRAVERSE_ALL, + -1, + (GNodeTraverseFunc)node_equal, + &bar); + if(bar==NULL) find=1; +#endif + if(find){ + printf("found 2#\n"); + } + } + + if(container->parent!=NULL){ + GNode* bar=container->parent->children; + + if(bar==NULL)printf("!!\n"); + if(bar==container){ + container->parent->children=container->next; + if(container->next!=NULL) + container->next->prev=NULL; + } + else{ + container->prev->next=container->next; + if(container->next!=NULL) + container->next->prev=container->prev; + } + } + + if(parent->children!=NULL){ + if(container->next!=NULL)printf("!\n"); + parent->children->prev=container; + container->next=parent->children; + } + parent->children=container; + container->parent=parent; + } + + /* dump(container, 0); */ +} + +static +void +find_root_set(gpointer key, + GNode* value, + GSList ** root_set){ + /* + * Walk over the elements of id_table, and gather a list of the + * Container objects that have no parents. + */ + + if(value->parent!=NULL) return; + *root_set=g_slist_append(*root_set, value); +} + +static +gboolean +prune(GNode *node, GSList *root_set){ + /* + printf("prune: %x, %x, %x, %x, %x\n", + (int)node,(int)(node->parent), + (int)(node->prev), + (int)(node->next),(int)(node->children)); + */ + + /* + * Recursively walk all containers under the root set. For each container: + * + * + If it is an empty container with no children, nuke it. + * + If the Container has no Message, but does have children, + * remove this container but promote its children to this level + * (that is, splice them in to the current child list.) + * + * Do not promote the children if doing so would promote them to + * the root set -- unless there is only one child, in which case, do. + */ + + if(node->data==NULL && node->children==NULL){ + /* + printf("node! %x, %x\n", (int)node, (int)(node->parent)); + */ + if(node->parent!=NULL){ + if(node->prev==NULL){ + node->parent->children=node->next; + if(node->next!=NULL) + node->next->prev=NULL; + } + else{ + node->prev->next=node->next; + if(node->next!=NULL) + node->next->prev=node->prev; + } + } + else{ + GSList* foo=root_set; + while(foo){ + if(foo->data==node){ + foo->data=NULL; + break; + } + foo=g_slist_next(foo); + } + } + return FALSE; + } + + if(node->children!=NULL && node->data==NULL && + (node->parent!=NULL || node->children->next==NULL)){ + if(node->parent==NULL){ + while(root_set){ + if(root_set->data==node){root_set->data=node->children; break;} + root_set=g_slist_next(root_set); + } + node->children->parent=NULL; + } + else{ + GNode* foo=node->parent->children; + /* + printf("prune: %x, parent(%x), next(%x), prev(%x), children(%x)\n", + (int)node, (int)(node->parent), + (int)(node->next), (int)(node->prev), (int)(node->children)); + */ + if(node->prev==NULL){ + node->parent->children=node->children; + } + else{ + node->prev->next=node->children; + node->children->prev=node->prev; + } + + foo=node->children; + while(foo->next!=NULL){ + foo->parent=node->parent; + foo=foo->next; + } + + if(foo==node->children){ + foo->parent=node->parent; + } + foo->next=node->next; + if(node->next!=NULL){ + node->next->prev=foo; + } + + { + GNode* bar=node->parent->children; + while(bar){ + if(bar==node){printf("find!!!");break;} + bar=bar->next; + } + } + } + return FALSE; + } + + return FALSE; +} + +static +gboolean +node_equal(GNode *node, GNode** parent){ + if(node==*parent){ + *parent=NULL; + return TRUE; + } + return FALSE; +} + + +static +void +dump(GNode *node, int indent){ + int i=indent; + GNode *children=node->children; + while(i){printf(" "); i--;} + printf("%x, %x(%s)%d\n", + (int)(node), + (int)(node->data), + ((node->data!=NULL) ? + /* ((LibBalsaMessage *)(((GNode*)(node)->data)))->message_id : */ + ((LibBalsaMessage *)(((GNode*)(node)->data)))->subject : + "empty"), + ((node->data!=NULL) ? + (int)(((LibBalsaMessage *)(((GNode*)(node)->data)))->msgno+1) : + -1) + ); + while(children){ + dump(children, indent+2); + children=children->next; + } +} + +static +gboolean +construct(GNode *node, GtkCTree *ctree){ + GtkCTreeNode *ctreenode=NULL; + GtkCTreeNode *ctreeparent=NULL; + GtkCTreeNode *sibling=NULL; + LibBalsaMessage *message=NULL; + + /* + printf("construct: %x, %x, %x, %x\n", (int)node,(int)(node->parent), + (int)(node->next),(int)(node->children)); + */ + + if(node->parent!=NULL && node->parent->data!=NULL){ + message=(LibBalsaMessage *)(node->parent->data); + ctreeparent=gtk_ctree_find_by_row_data(ctree, NULL, (gpointer) message); + } + + if(node->data!=NULL){ + message=(LibBalsaMessage *)(node->data); + ctreenode=gtk_ctree_find_by_row_data(ctree, NULL, (gpointer) message); + if(ctreenode==NULL) + printf("null!! 1\n"); + gtk_ctree_move(ctree, ctreenode, ctreeparent, NULL); + } + + if(node->next!=NULL){ + message=(LibBalsaMessage *)(node->next->data); + sibling=gtk_ctree_find_by_row_data(ctree, NULL, (gpointer) message); + if(sibling==NULL) + printf("null!! 2 message=%x\n", (int)message); + gtk_ctree_move(ctree, sibling, ctreeparent, ctreenode); + } + + if(node->children!=NULL){ + GNode *children=node->children; + GtkCTreeNode *foo=NULL; + while(children){ + message=(LibBalsaMessage *)(children->data); + foo=gtk_ctree_find_by_row_data(ctree, NULL, (gpointer) message); + if(foo!=NULL){ + gtk_ctree_move(ctree, foo, ctreenode, NULL); + } + children=children->next; + } + } + + return FALSE; +} + +static +void +subject_gather(GNode *node, GHashTable* subject_table){ + LibBalsaMessage *message=NULL; + gchar *subject=NULL; + gchar *chopped_subject=NULL; + GNode* old; + + /* + * If any two members of the root set have the same subject, merge them. + * This is so that messages which don't have References headers at all + * still get threaded (to the extent possible, at least.) + * + * + Construct a new hash table, subject_table, which associates subject + * strings with Container objects. + * + * + For each Container in the root set: + * + * Find the subject of that sub-tree: + * + If there is a message in the Container, the subject is the subject of + * that message. + * + If there is no message in the Container, then the Container will have + * at least one child Container, and that Container will have a message. + * Use the subject of that message instead. + * + Strip ``Re:'', ``RE:'', ``RE[5]:'', ``Re: Re[4]: Re:'' and so on. + * + If the subject is now "", give up on this + * + Add this Container to the subject_table if: Container. + * + There is no container in the table with this subject, or + * + This one is an empty container and the old one is not: the empty + * one is more interesting as a root, so put it in the table instead. + * + The container in the table has a ``Re:'' version of this subject, + * and this container has a non-``Re:'' version of this subject. + * The non-re version is the more interesting of the two. + * + * + Now the subject_table is populated with one entry for each subject + * which occurs in the root set. Now iterate over the root set, + * and gather together the difference. + * + * For each Container in the root set: + * + * Find the subject of this Container (as above.) + * Look up the Container of that subject in the table. + * If it is null, or if it is this container, continue. + * Otherwise, we want to group together this Container and the one + * in the table. There are a few possibilities: + * + If both are dummies, append one's children to the other, and + * remove the now-empty container. + * + * + If one container is a empty and the other is not, make the + * non-empty one be a child of the empty, and a sibling of the + * other ``real'' messages with the + * same subject (the empty's children.) + * + If that container is a non-empty, and that message's subject + * does not begin with ``Re:'', but this message's subject does, + * then make this be a child of the other. + * + If that container is a non-empty, and that message's subject + * begins with ``Re:'', but this message's subject does not, + * then make that be a child of this one -- they were misordered. + * (This happens somewhat implicitly, since if there are two + * messages, one with Re: and one without, the one without + * will be in the hash table, regardless of the order in which + * they were seen.) + * + * + Otherwise, make a new empty container and make both msgs be + * a child of it. This catches the both-are-replies and + * neither-are-replies cases, and makes them be siblings instead of + * asserting a hierarchical relationship which might not be true. + * + * (People who reply to messages without using ``Re:'' and without + * using a References line will break this slightly. Those people suck.) + * + * (It has occurred to me that taking the date or message number into + * account would be one way of resolving some of the ambiguous cases, + * but that's not altogether straightforward either.) + */ + + if(node==NULL) return; + + message=(LibBalsaMessage *)(node->data!=NULL ? + node->data : + node->children->data); + if(message==NULL){ + printf("???\n"); + return; + } + + subject=message->subject; + if(subject==NULL)return; + chopped_subject=chop_re(subject); + if(chopped_subject==NULL) { + return; + } + + /* + printf("subject_gather: subject %s, %s\n", subject, chopped_subject); + */ + + old=g_hash_table_lookup(subject_table, chopped_subject); + if(old==NULL || + (node->data==NULL&&old->data!=NULL)){ + g_hash_table_insert(subject_table, chopped_subject, node); + return; + } + + { + LibBalsaMessage *old_message=(LibBalsaMessage *)(old->data!=NULL ? + old->data : + old->children->data); + if(old_message==NULL){ + printf("????\n"); + return; + } + + if(old_message->subject!=chop_re(old_message->subject) && + subject==chopped_subject){ + g_hash_table_insert(subject_table, chopped_subject, node); + } + } + return; +} + +static +void +subject_merge(GNode *node, GHashTable* subject_table, + GSList* root_set, GSList** save_node){ + LibBalsaMessage *message=NULL; + gchar *subject=NULL; + gchar *chopped_subject=NULL; + GNode* node2; + + if(node==NULL) return; + + message=(LibBalsaMessage *)(node->data!=NULL ? + node->data : + node->children->data); + if(message==NULL){ + printf("???\n"); + return; + } + + subject=message->subject; + if(subject==NULL)return; + chopped_subject=chop_re(subject); + if(chopped_subject==NULL) { + return; + } + + node2=g_hash_table_lookup(subject_table, chopped_subject); + if(node2==NULL || node2==node){ + return; + } + + if(node->data==NULL && node2->data==NULL){ + reparent(node2, node->children); + node->children=NULL; + return; + } + + if(node->data==NULL && node2->data!=NULL){ + reparent(node, node2->children); + node2->children=NULL; + return; + } + if(node2->data==NULL && node->data!=NULL){ + reparent(node2, node->children); + node->children=NULL; + return; + } + + if(node2->data!=NULL){ + LibBalsaMessage *message2=(LibBalsaMessage *)(node2->data); + gchar* chopped_subject2=chop_re(message2->subject); + if((message2->subject==chopped_subject2) && + subject!=chopped_subject){ + reparent(node2, node); + { + GSList* foo=root_set; + while(foo){ + if(foo->data==node){ + foo->data=NULL; + } + foo=g_slist_next(foo); + } + } + return; + } + if((message2->subject!=chopped_subject2) && + subject==chopped_subject){ + reparent(node, node2); + g_hash_table_insert(subject_table, chopped_subject, node); + { + GSList* foo=root_set; + while(foo){ + if(foo->data==node2){ + foo->data=NULL; + } + foo=g_slist_next(foo); + } + } + return; + } + } + + { + GNode *new_node=g_node_new(NULL); + GSList* foo=root_set; + while(foo){ + if(foo->data==node){ + foo->data=new_node; + } + else if(foo->data==node2){ + foo->data=NULL; + } + foo=g_slist_next(foo); + } + reparent(new_node, node); + reparent(new_node, node2); + + *save_node=g_slist_append(*save_node, new_node); + } + return; +} + +static +void +reparent(GNode* node, GNode* children){ + GNode* p=children; + while(p){ + p->parent=node; + p=p->next; + } + p=node->children; + if(p!=NULL){ + while(p->next)p=p->next; + p->next=children; + if(children!=NULL)children->prev=p; + } + else{ + node->children=children; + } +} + +/* The more heuristics should be added. */ +static +gchar * +chop_re(gchar* str){ + gchar *p=str; + while(*p){ + while(*p && *p==' ') p++; + if(*p && g_strncasecmp(p, "RE:", 3)==0){ + p+=3; + continue; + } + break; + } + return p; +} + +static +void +free_node(gpointer key, GNode* node, gpointer data){ + node->data=NULL; + node->children=NULL; + g_node_destroy(node); +} + +/* yet another message threading function */ +static +void +threading_simple(BalsaIndex* bindex){ + LibBalsaMessage* message; + GtkCTreeNode* sibling; + GtkCTreeNode* parent; + GList *root_children=NULL; + GList *p=NULL; + GtkCTree* ctree=GTK_CTREE(bindex); + GHashTable *msg_table; + + msg_table=g_hash_table_new(g_str_hash, g_str_equal); + + gtk_ctree_pre_recursive(ctree, + (GtkCTreeNode *)NULL, + (GtkCTreeFunc)add_message, + (gpointer)msg_table); + sibling=gtk_ctree_node_nth(ctree, 0); + while(sibling!=NULL){ + root_children=g_list_append(root_children, sibling); + sibling=GTK_CTREE_ROW(sibling)->sibling; + } + p=root_children; + while(p){ + parent=NULL; + message=MESSAGE(p->data); + if(message->references_for_threading!=NULL){ + parent= + g_hash_table_lookup(msg_table, + g_list_last(message->references_for_threading)->data); + if(parent!=NULL){ + if(p->data==NULL) + printf("null!! 4\n"); + else + gtk_ctree_move(ctree, p->data, parent, NULL); + } + } + p=g_list_next(p); + } + g_list_free(root_children); + g_hash_table_destroy(msg_table); +} + +static +void +add_message(GtkCTree *ctree, GtkCTreeNode *node, GHashTable *msg_table){ + LibBalsaMessage* message=MESSAGE(node); + GtkCTreeNode *foo=NULL; + if(!message->message_id) + return; + foo=g_hash_table_lookup(msg_table, message->message_id); + if(foo==NULL){ + g_hash_table_insert(msg_table, message->message_id, node); + } +} diff -Naur balsa-1.0.0/src/balsa-index-threading.h balsa-1.0.0-threading/src/balsa-index-threading.h --- balsa-1.0.0/src/balsa-index-threading.h Thu Jan 1 00:00:00 1970 +++ balsa-1.0.0-threading/src/balsa-index-threading.h Thu Nov 16 14:46:03 2000 @@ -0,0 +1,43 @@ +/* Balsa E-Mail Client + * Copyright (C) 1997-1999 Jay Painter and Stuart Parmenter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef __BALSA_INDEX_THREADING_H__ +#define __BALSA_INDEX_THREADING_H__ + +#include +#include "libbalsa.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +typedef enum +{ + BALSA_INDEX_THREADING_FLAT, + BALSA_INDEX_THREADING_SIMPLE, + BALSA_INDEX_THREADING_JWZ +} BalsaIndexThreadingType; + + void balsa_index_threading(BalsaIndex * bindex); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __BALSA_INDEX_H__ */ diff -Naur balsa-1.0.0/src/balsa-index.c balsa-1.0.0-threading/src/balsa-index.c --- balsa-1.0.0/src/balsa-index.c Thu Nov 16 14:47:15 2000 +++ balsa-1.0.0-threading/src/balsa-index.c Thu Nov 16 14:46:03 2000 @@ -39,6 +39,7 @@ #include "balsa-app.h" #include "balsa-icons.h" #include "balsa-index.h" +#include "balsa-index-threading.h" #include "main-window.h" /* constants */ @@ -88,10 +89,10 @@ gpointer data); static void button_event_release_cb(GtkCList * clist, GdkEventButton * event, gpointer data); -static void select_message(GtkWidget * widget, gint row, gint column, - GdkEventButton * bevent, gpointer data); -static void unselect_message(GtkWidget * widget, gint row, gint column, - GdkEventButton * bevent, gpointer data); +static void select_message(GtkWidget * widget, GtkCTreeNode *row, gint column, + gpointer data); +static void unselect_message(GtkWidget * widget, GtkCTreeNode *row, gint column, + gpointer data); static void resize_column_event_cb(GtkCList * clist, gint column, gint width, gpointer data); @@ -183,7 +184,7 @@ }; balsa_index_type = - gtk_type_unique(gtk_clist_get_type(), &balsa_index_info); + gtk_type_unique(gtk_ctree_get_type(), &balsa_index_info); } return balsa_index_type; @@ -242,7 +243,8 @@ list = clist->selection; while (list) { - i = GPOINTER_TO_INT(list->data); + i = gtk_clist_find_row_from_data(clist, + LIBBALSA_MESSAGE(gtk_ctree_node_get_row_data(GTK_CTREE(clist), list->data))); if (i > h) h = i; list = g_list_next(list); @@ -304,9 +306,10 @@ titles[5] = _("Date"); bindex->mailbox = NULL; + bindex->threading_type=BALSA_INDEX_THREADING_JWZ; /* create the clist */ - gtk_clist_construct(GTK_CLIST(bindex), 6, titles); + gtk_ctree_construct (GTK_CTREE(bindex), 6, 4, titles); clist = GTK_CLIST(bindex); gtk_signal_connect(GTK_OBJECT(clist), "click_column", @@ -332,11 +335,11 @@ gtk_clist_set_sort_type(clist, GTK_SORT_DESCENDING); gtk_signal_connect(GTK_OBJECT(clist), - "select-row", + "tree-select-row", (GtkSignalFunc) select_message, (gpointer) bindex); gtk_signal_connect(GTK_OBJECT(clist), - "unselect-row", + "tree-unselect-row", (GtkSignalFunc) unselect_message, (gpointer) bindex); @@ -377,8 +380,11 @@ return TRUE; gdk_threads_enter(); - gtk_clist_moveto(GTK_CLIST(bindex), bindex->first_new_message - 1, -1, - 0.5, 0.0); + if(bindex->first_new_message!=NULL) + gtk_clist_moveto(GTK_CLIST (bindex), + gtk_clist_find_row_from_data(GTK_CLIST (bindex), + bindex->first_new_message), + -1, 0.0, 0.0); gdk_threads_leave(); return FALSE; @@ -449,13 +455,15 @@ i++; } + balsa_index_threading(bindex); gtk_clist_sort(GTK_CLIST(bindex)); DO_CLIST_WORKAROUND(GTK_CLIST(bindex)) gtk_clist_thaw(GTK_CLIST(bindex)); /* FIXME this might could be cleaned up some */ - if (bindex->first_new_message == 0) - bindex->first_new_message = i; + if(bindex->first_new_message==NULL && i) + bindex->first_new_message= + LIBBALSA_MESSAGE(gtk_clist_get_row_data(GTK_CLIST (bindex), i-1)); gtk_idle_add((GtkFunction) moveto_handler, bindex); } @@ -465,7 +473,7 @@ { gchar buff1[32]; gchar *text[6]; - gint row; + GtkCTreeNode *node; GList *list; LibBalsaAddress *addy = NULL; @@ -507,17 +515,20 @@ text[5] = libbalsa_message_date_to_gchar(message, balsa_app.date_string); - row = gtk_clist_append(GTK_CLIST(bindex), text); + node=gtk_ctree_insert_node(GTK_CTREE(bindex), NULL, NULL, text, 2, + NULL, NULL, NULL, NULL, FALSE, TRUE); g_free(text[5]); - gtk_clist_set_row_data(GTK_CLIST(bindex), row, (gpointer) message); + gtk_ctree_node_set_row_data (GTK_CTREE (bindex), node, (gpointer) message); - clist_set_col_img_from_flag(bindex, row, message); + clist_set_col_img_from_flag(bindex, + gtk_clist_find_row_from_data (GTK_CLIST (bindex), message), + message); - if (bindex->first_new_message == 0) + if (bindex->first_new_message == NULL) if (message->flags & LIBBALSA_MESSAGE_FLAG_NEW) - bindex->first_new_message = row + 1; + bindex->first_new_message = message; /*gtk_clist_sort(GTK_CLIST(bindex));*/ DO_CLIST_WORKAROUND(GTK_CLIST(bindex)); @@ -526,7 +537,7 @@ void balsa_index_del(BalsaIndex * bindex, LibBalsaMessage * message) { - gint row; + GtkCTreeNode *node; g_return_if_fail(bindex != NULL); g_return_if_fail(message != NULL); @@ -534,16 +545,40 @@ if (bindex->mailbox == NULL) return; - row = - gtk_clist_find_row_from_data(GTK_CLIST(bindex), - (gpointer) message); - if (row < 0) + node=gtk_ctree_find_by_row_data (GTK_CTREE (bindex), NULL, (gpointer) message); + if (node == NULL) return; - if (row == (bindex->first_new_message)) - bindex->first_new_message = 0; + if(bindex->first_new_message==message){ + bindex->first_new_message=NULL; + } - gtk_clist_remove(GTK_CLIST(bindex), row); + { + GtkCTreeNode *children=GTK_CTREE_ROW(node)->children; + if(children!=NULL){ + GtkCTreeNode *sibling=GTK_CTREE_ROW(node)->parent; + GtkCTreeNode *next; + while(sibling!=NULL){ + if(GTK_CTREE_ROW(sibling)->parent==NULL)break; + sibling=GTK_CTREE_ROW(sibling)->parent; + } + + if(sibling!=NULL)sibling=GTK_CTREE_ROW(sibling)->sibling; + else{sibling=GTK_CTREE_ROW(node)->sibling;} + + while(1){ + if(children==NULL)break; + next=GTK_CTREE_ROW(children)->sibling; + gtk_ctree_move(GTK_CTREE (bindex), children, NULL, sibling); + children=next; + } + node=gtk_ctree_find_by_row_data (GTK_CTREE (bindex), + NULL, + (gpointer) message); + } + } + + gtk_ctree_remove_node(GTK_CTREE(bindex), node); } @@ -671,7 +706,8 @@ list = clist->selection; while (list) { /* look for the selected row with the lowest number */ - i = GPOINTER_TO_INT(list->data); + i = gtk_clist_find_row_from_data(clist, + LIBBALSA_MESSAGE(gtk_ctree_node_get_row_data(GTK_CTREE(clist), list->data))); if (i < h) h = i; list = list->next; @@ -703,7 +739,8 @@ if (!clist->selection) return; - h = GPOINTER_TO_INT(g_list_first(clist->selection)->data); + h = gtk_clist_find_row_from_data(clist, + LIBBALSA_MESSAGE(gtk_ctree_node_get_row_data(GTK_CTREE(clist), g_list_first(clist->selection)->data))); gtk_clist_select_row(clist, h, -1); if (gtk_clist_row_is_visible(clist, h) != GTK_VISIBILITY_FULL) @@ -810,38 +847,38 @@ } static void -select_message(GtkWidget * widget, gint row, gint column, - GdkEventButton * bevent, gpointer data) +select_message(GtkWidget * widget, GtkCTreeNode *row, gint column, + gpointer data) { BalsaIndex *bindex; LibBalsaMessage *message; bindex = BALSA_INDEX(data); - message = - LIBBALSA_MESSAGE(gtk_clist_get_row_data(GTK_CLIST(widget), row)); + message = + LIBBALSA_MESSAGE(gtk_ctree_node_get_row_data (GTK_CTREE(widget), row)); if (message) { gtk_signal_emit(GTK_OBJECT(bindex), balsa_index_signals[SELECT_MESSAGE], - message, bevent); + message, NULL); } } static void -unselect_message(GtkWidget * widget, gint row, gint column, - GdkEventButton * bevent, gpointer data) +unselect_message(GtkWidget * widget, GtkCTreeNode *row, gint column, + gpointer data) { BalsaIndex *bindex; LibBalsaMessage *message; bindex = BALSA_INDEX(data); message = - LIBBALSA_MESSAGE(gtk_clist_get_row_data(GTK_CLIST(widget), row)); + LIBBALSA_MESSAGE(gtk_ctree_node_get_row_data (GTK_CTREE(widget), row)); if (message) gtk_signal_emit(GTK_OBJECT(bindex), balsa_index_signals[UNSELECT_MESSAGE], - message, bevent); + message, NULL); } /* When a column is resized, store the new size for later use */ @@ -897,6 +934,7 @@ gtk_clist_freeze(GTK_CLIST (bindex)); balsa_index_add(bindex, message); if(bindex->mailbox->new_messages==0){ + balsa_index_threading(bindex); gtk_clist_sort (GTK_CLIST (bindex)); } DO_CLIST_WORKAROUND(GTK_CLIST (bindex)); @@ -916,6 +954,7 @@ balsa_index_add(bindex, message); messages=g_list_next(messages); } + balsa_index_threading(bindex); gtk_clist_sort (GTK_CLIST (bindex)); DO_CLIST_WORKAROUND(GTK_CLIST (bindex)); gtk_clist_thaw (GTK_CLIST (bindex)); @@ -954,13 +993,13 @@ * */ void -balsa_index_get_selected_rows(BalsaIndex * bindex, guint ** rows, +balsa_index_get_selected_rows(BalsaIndex * bindex, GtkCTreeNode ***rows, guint * nb_rows) { GList *list_of_selected_rows; GtkCList *clist; guint nb_selected_rows; - guint *selected_rows; + GtkCTreeNode **selected_rows; guint row_count; clist = GTK_CLIST(bindex); @@ -969,9 +1008,9 @@ list_of_selected_rows = clist->selection; nb_selected_rows = g_list_length(list_of_selected_rows); - selected_rows = (guint *) g_malloc(nb_selected_rows * sizeof(guint)); + selected_rows = (GtkCTreeNode **) g_malloc(nb_selected_rows * sizeof(GtkCTreeNode *)); for (row_count = 0; row_count < nb_selected_rows; row_count++) { - selected_rows[row_count] = (guint) (list_of_selected_rows->data); + selected_rows[row_count] = (GtkCTreeNode *) (list_of_selected_rows->data); list_of_selected_rows = list_of_selected_rows->next; } @@ -1019,6 +1058,7 @@ i++; } + balsa_index_threading(bindex); gtk_clist_sort(GTK_CLIST(bindex)); DO_CLIST_WORKAROUND(GTK_CLIST(bindex)) diff -Naur balsa-1.0.0/src/balsa-index.h balsa-1.0.0-threading/src/balsa-index.h --- balsa-1.0.0/src/balsa-index.h Fri Oct 6 15:12:28 2000 +++ balsa-1.0.0-threading/src/balsa-index.h Thu Nov 16 14:46:03 2000 @@ -38,14 +38,15 @@ typedef struct _BalsaIndexClass BalsaIndexClass; struct _BalsaIndex { - GtkCList clist; + GtkCTree ctree; LibBalsaMailbox *mailbox; - guint first_new_message; + LibBalsaMessage *first_new_message; + int threading_type; }; struct _BalsaIndexClass { - GtkCListClass parent_class; + GtkCTreeClass parent_class; void (*select_message) (BalsaIndex * bindex, LibBalsaMessage * message); @@ -81,7 +82,7 @@ /* retrieve the selection */ extern void balsa_index_get_selected_rows(BalsaIndex * bindex, - guint ** rows, + GtkCTreeNode *** rows, guint * nb_rows); #ifdef __cplusplus diff -Naur balsa-1.0.0/src/main-window.c balsa-1.0.0-threading/src/main-window.c --- balsa-1.0.0/src/main-window.c Sat Nov 11 16:19:49 2000 +++ balsa-1.0.0-threading/src/main-window.c Thu Nov 16 14:46:03 2000 @@ -2018,11 +2018,10 @@ balsa_index_update_message(BALSA_INDEX_PAGE(index_page)); if (GTK_CLIST(index)->selection) { - message = message = gtk_clist_get_row_data(GTK_CLIST(index), - GPOINTER_TO_INT - (GTK_CLIST - (index)->selection-> - data)); + message = gtk_ctree_node_get_row_data(GTK_CTREE(index), + (GTK_CLIST + (index)->selection-> + data)); enable_message_menus(message); } else { enable_message_menus(NULL);