/*
 * grn_threadlist.c: threadlist window
 *
 * $Id: grn_threadlist.c,v 1.37 2000/06/05 15:35:12 sc Exp $
 */
/* Copyright (C) 1999-2000  Sergey Chernikov (sc@ivvs.ul.ru)
 *
 * Authors: Sergey Chernikov <sc@ivvs.ul.ru>
 *
 * 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 of the License, 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
 */

#include <math.h>
#include "grn_consts.h"
#include <gnome.h>
#include "grn_threadlist.h"
#include "grn_menu.h"
#include "grn_vars.h"
#include "grn_util.h"
#include "grn_misc.h"
#include "grn_config.h"
#include "grn_commands.h"
#include "nntp.h"
#include "grn_news.h"
#include "grn_nglist.h"
#include "grn_msgwin.h"
#include "grn_perl.h"
#include "queue.h"


#define NCOLS	8


static void evt_tl_delete(GtkWidget *, gpointer);
static void evt_tl_hide(GtkWidget *, gpointer);
static void evt_tl_show(GtkWidget *, gpointer);
static gint evt_tl_keypress(GtkWidget *, GdkEventKey *, gpointer);
static gint evt_tl_btnpress(GtkWidget *, GdkEventButton *, gpointer);
static void evt_click_col(GtkCList *, int);


#include "grn_tl_compare_funcs.c"
static GList *sort_msghdrlist(GList *msghdr_list)
{
  GList *ret = NULL;
  g_return_val_if_fail(msghdr_list != NULL, NULL);

  grn_appbar_push(GRN->appbar, FALSE, _("Sorting articles..."));
  
  if (grn_prefs.thrd_tl_type > 0)
    ret = g_list_sort(msghdr_list, hdr_compare_dates);
  else {
    guint n;
    GCompareFunc compare_func;

    n = grn_prefs.srt_tl_col;
    if (n >= 100)  n -= 100;
    switch (n)
    {
      case 1:
        compare_func = hdr_compare_scores;
        break;
      case 3:
        compare_func = hdr_compare_subjects;
        break;
      case 4:
        compare_func = hdr_compare_authors;
        break;
      case 5:
        compare_func = hdr_compare_dates;
        break;
      case 6:
        compare_func = hdr_compare_lines;
        break;
      case 7:
        compare_func = hdr_compare_sizes;
        break;
      default:
        compare_func = hdr_compare_dates;
    }
    ret = g_list_sort(msghdr_list, compare_func);
    if (grn_prefs.srt_tl_col >= 100)  ret = g_list_reverse(ret);
  }
  grn_appbar_pop(GRN->appbar);
  return ret;
}

static GList *sort_msglist_threads(GList *msglist)
{
  guint n;
  GList *ret = NULL;
  GCompareFunc compare_func;
  if (! msglist)  return NULL;

  grn_appbar_push(GRN->appbar, FALSE, _("Sorting threads..."));

  n = grn_prefs.srt_tl_col;
  if (n >= 100)  n -= 100;
  switch (n)
  {
    case 1:
      compare_func = compare_scores;
      break;
    case 3:
      compare_func = compare_subjects;
      break;
    case 4:
      compare_func = compare_authors;
      break;
    case 5:
      compare_func = compare_dates;
      break;
    case 6:
      compare_func = compare_lines;
      break;
    case 7:
      compare_func = compare_sizes;
      break;
    default:
      compare_func = no_sort;
  }
  ret = g_list_sort(msglist, compare_func);
  if (grn_prefs.srt_tl_col >= 100)  ret = g_list_reverse(ret);

  grn_appbar_pop(GRN->appbar);
  return ret;
}


static GList *thread_by_nothing(GList *msghdr_list)
{
  GList *l, *l_end=NULL, *ret=NULL;
  gulong cnt=0, total, step;

  total = g_list_length(msghdr_list);
  step = total / 100;
  if (step <= 0)  step = 1;
  for (l=msghdr_list; l; l=l->next)
  {
    t_msgheaders *mh = (t_msgheaders *) l->data;
    grn_newsgroup *grp = mh->grp;
    t_message *msg;

    cnt++;
    if ((! grn_prefs.tl_show_read) && grp && grp->seq)
    {
        if (grn_news_art_read(grp->seq, mh->number))  continue;
    }
    msg = t_message_alloc();
    msg->mh = t_msgheaders_copy(mh);
    grn_g_list_append(&ret, &l_end, msg);

    if (! (cnt % step))
    {
      grn_lock();
      gnome_appbar_set_progress(GRN->appbar, cnt / ((gfloat) total));
      grn_unlock();
    }
  }
  return ret;
}

static GList *thread_by_ref(GList *msghdr_list)
{
  GList *l, *l_end=NULL, *ret=NULL;
  gulong cnt=0, total, step;

  total = g_list_length(msghdr_list);
  step = total / 100;
  if (step <= 0)  step = 1;
  for (l=msghdr_list; l; l=l->next)
  {
    t_msgheaders *mh = (t_msgheaders *) l->data;
    t_message *msg, *parent_msg;

    cnt++;
    msg = t_message_alloc();
    msg->mh = t_msgheaders_copy(mh);
    parent_msg = msglist_find_by_id(ret, msg);
    if (parent_msg)  parent_msg->threads = g_list_append(parent_msg->threads, msg);
    else  grn_g_list_append(&ret, &l_end, msg);

    if (! (cnt % step))
    {
      grn_lock();
      gnome_appbar_set_progress(GRN->appbar, cnt / ((gfloat) total));
      grn_unlock();
    }
  }
  return ret;
}

static GList *thread_by_subj(GList *msghdr_list)
{
  GList *l, *l_end=NULL, *ret=NULL;
  gulong cnt=0, total, step;

  total = g_list_length(msghdr_list);
  step = total / 100;
  if (step <= 0)  step = 1;
  for (l=msghdr_list; l; l=l->next)
  {
    t_msgheaders *mh = (t_msgheaders *) l->data;
    t_message *msg, *parent_msg;

    cnt++;
    msg = t_message_alloc();
    msg->mh = t_msgheaders_copy(mh);
    parent_msg = msglist_find_by_subj(ret, msg);
    if (parent_msg)  parent_msg->threads = g_list_append(parent_msg->threads, msg);
    else  grn_g_list_append(&ret, &l_end, msg);

    if (! (cnt % step))
    {
      grn_lock();
      gnome_appbar_set_progress(GRN->appbar, cnt / ((gfloat) total));
      grn_unlock();
    }
  }
  return ret;
}

static GList *thread_by_ref_and_subj(GList *msghdr_list)
{
  GList *l, *l_end=NULL, *ret=NULL;
  gulong cnt=0, total, step;

  total = g_list_length(msghdr_list);
  step = total / 100;
  if (step <= 0)  step = 1;
  for (l=msghdr_list; l; l=l->next)
  {
    t_msgheaders *mh = (t_msgheaders *) l->data;
    t_message *msg, *parent_msg;

    cnt++;
    msg = t_message_alloc();
    msg->mh = t_msgheaders_copy(mh);
    parent_msg = msglist_find_by_id(ret, msg);
    if (parent_msg)  parent_msg->threads = g_list_append(parent_msg->threads, msg);
    else {
      parent_msg = msglist_find_by_subj(ret, msg);
      if (parent_msg)  parent_msg->threads = g_list_append(parent_msg->threads, msg);
      else  grn_g_list_append(&ret, &l_end, msg);
    }
    if (! (cnt % step))
    {
      grn_lock();
      gnome_appbar_set_progress(GRN->appbar, cnt / ((gfloat) total));
      grn_unlock();
    }
  }
  return ret;
}

static GList *thread_messages(GList *msghdr_list, gboolean must_sort)
{
  GList *ret=NULL, *ret1=NULL;
  g_return_val_if_fail(msghdr_list != NULL, NULL);
  
  if (must_sort)  ret1 = sort_msghdrlist(msghdr_list);
  else  ret1 = msghdr_list;

  grn_appbar_push(GRN->appbar, FALSE, _("Threading articles..."));

  if (grn_prefs.thrd_tl_type == 0)  ret = thread_by_nothing(ret1);
  else if (grn_prefs.thrd_tl_type == 1)  ret = thread_by_ref(ret1);
  else if (grn_prefs.thrd_tl_type == 2)  ret = thread_by_subj(ret1);
  else if (grn_prefs.thrd_tl_type == 3)  ret = thread_by_ref_and_subj(ret1);

  grn_appbar_pop(GRN->appbar);
  grn_lock();
  gnome_appbar_set_progress(GRN->appbar, 0);
  grn_unlock();

  return ret;
}

static GtkCTreeNode *add_msg_to_ctree(GtkCTree *ctree, GtkCTreeNode *parent,
				      t_message *msg)
{
  gchar *row[NCOLS];
  GtkCTreeNode *node;
  gint i;
  grn_newsgroup *grp;
  GdkPixmap *pixmap=NULL, *score_pm=NULL;
  GdkBitmap *mask=NULL, *score_mask=NULL;
  g_return_val_if_fail(msg != NULL, NULL);
  g_return_val_if_fail(msg->mh != NULL, NULL);
  g_return_val_if_fail(msg->mh->grp != NULL, NULL);
  
  grp = msg->mh->grp;

  if (! t_message_is_read(msg))
  {
    pixmap = GRN->pixmaps.unread_article.pixmap;
    mask = GRN->pixmaps.unread_article.mask;
  }
  
  if ((msg->mh->score >= 1) && (msg->mh->score <= 16))
  {
    score_pm = GRN->pixmaps.balls[msg->mh->score].pixmap;
    score_mask = GRN->pixmaps.balls[msg->mh->score].mask;
  }
  else if (msg->mh->score == 99)
  {
    score_pm = GRN->pixmaps.balls[0].pixmap;
    score_mask = GRN->pixmaps.balls[0].mask;
  }
  
  row[0] = row[1] = row[2] = NULL;
  if ((! score_pm) && (msg->mh->score > 0))
    row[1] = g_strdup_printf("%u", msg->mh->score);
  row[3] = str_copy(msg->mh->subject);
  row[4] = str_copy(msg->mh->from);
  row[5] = date_get_local(msg->mh->parsed_date);
  row[6] = g_strdup_printf("%ld", msg->mh->lines);
  row[7] = g_strdup_printf("%ld", msg->mh->size);
  
  grn_lock();
  if ((! parent) && (msg->threads))
    node = gtk_ctree_insert_node(ctree, NULL, NULL, row, 3,
           GRN->pixmaps.thread_closed.pixmap, GRN->pixmaps.thread_closed.mask,
	   GRN->pixmaps.thread_opened.pixmap, GRN->pixmaps.thread_opened.mask,
	   FALSE, TRUE);
  else  node = gtk_ctree_insert_node(ctree, parent, NULL, row, 0,
               NULL, NULL, NULL, NULL, FALSE, TRUE);
  if (pixmap)  gtk_ctree_node_set_pixmap(ctree, node, 2, pixmap, mask);
  if (score_pm)  gtk_ctree_node_set_pixmap(ctree, node, 1, score_pm, score_mask);
  gtk_ctree_node_set_row_data(ctree, node, msg);
  if (msg->mh->score == 99)  gtk_ctree_node_set_selectable(ctree, node, FALSE);
  grn_unlock();
  
  for (i=0; i<NCOLS; i++)  str_free(&row[i]);

  return node;
}
static void build_msgs_tree_recursive(GtkCTree *ctree, GtkCTreeNode *parent, GList *msgs)
{
  GList *l;
  
  for (l=msgs; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    GtkCTreeNode *sibling;
    sibling = add_msg_to_ctree(ctree, parent, msg);
    if (msg->threads)  build_msgs_tree_recursive(ctree, sibling, msg->threads);
  }
}

static void collapse_root_nodes(GtkCList *clist)
{
  GList *l;

  grn_lock();
  for (l=clist->row_list; l; l=l->next)
  {
    GtkCTreeNode *node = GTK_CTREE_NODE(l);
    gtk_ctree_collapse(GTK_CTREE(clist), node);
  }
  grn_unlock();
}

static void get_unread_stats_recursive(GList *msglist, gulong *nunread)
{
  GList *l;
  if (! msglist)  return;
  
  for (l=msglist; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    if (! msg)  continue;
    
    if (! t_message_is_read(msg))  (*nunread)++;
    get_unread_stats_recursive(msg->threads, nunread);
  }
}

void get_unread_tl_stats(GList *msglist, grn_threadlist_stats *ret)
{
  GList *l;
  
  ret->nunread = 0;
  ret->nthunread = 0;
  for (l=msglist; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    gulong nunr1 = 0;
    if (! msg)  continue;

    if (! t_message_is_read(msg))  nunr1++;
    get_unread_stats_recursive(msg->threads, &nunr1);
    ret->nunread += nunr1;
    if (nunr1 > 0)  ret->nthunread++;
  }
}

static void get_stats_recursive(GList *msglist, gulong *nchildren, gulong *nmsgs, gulong *nunread)
{
  GList *l;
  if (! msglist)  return;
  
  for (l=msglist; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    if (! msg)  continue;
    
    (*nmsgs)++;
    if (! t_message_is_read(msg))  { (*nunread)++;  (*nchildren)++; }
    get_stats_recursive(msg->threads, nchildren, nmsgs, nunread);
  }
}
static void get_tl_stats(GList *msglist, grn_threadlist_stats *ret)
{
  gulong nmsgs=0, nunread=0, nthreads=0, nchildren;
  GList *l;
  
  for (l=msglist; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    gboolean unread = FALSE;
    if (! msg)  continue;

    nthreads++;  nmsgs++;
    nchildren = 0;
    unread = (! t_message_is_read(msg));
    if (unread)  nunread++;
    if (msg->threads)
    {
      if (unread)  nchildren++;
      get_stats_recursive(msg->threads, &nchildren, &nmsgs, &nunread);
    }
    msg->nchildren = nchildren;
  }
  ret->nthreads = nthreads;  ret->nmsgs = nmsgs;  ret->nunread = nunread;
}

void show_tl_stats(grn_threadlist_stats *tls)
{
  if (! tls)  return;

  grn_appbar_set_status(GRN->appbar, _("Total %ld message[s] in %ld thread[s]. %ld unread message[s] in %ld thread[s]"),
                        tls->nmsgs, tls->nthreads, tls->nunread, tls->nthunread);
}

static void show_threads_nchildren(GtkCList *clist)
{
  gint i;
  gchar buf[100];
  g_return_if_fail(clist != NULL);
  
  for (i=0; i<clist->rows; i++)
  {
    t_message *msg;
    grn_lock();
    msg = (t_message *) gtk_clist_get_row_data(clist, i);
    grn_unlock();
    if (! msg)  continue;
    
    if (msg->nchildren > 0)
    {
      snprintf(buf, 99, "%d", msg->nchildren);
      grn_lock();
      gtk_clist_set_text(clist, i, 0, buf);
      grn_unlock();
    }
  }
}

static GList *leave_unread(GList *msglist)
{
  GList *l, *ret=NULL;
  if (! msglist)  return NULL;
  
  for (l=msglist; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    if ((! msg) || (! msg->mh))  continue;
    
    msg->threads = leave_unread(msg->threads);
    if ((t_message_is_read(msg) || (msg->mh->score == 99)) && (! msg->threads))
    {
      t_message_free(&msg);
      continue;
    }
    ret = g_list_prepend(ret, msg);
  }
  g_list_free(msglist);
  ret = g_list_reverse(ret);
  return ret;
}


void threadlist_refresh(GtkWidget *threadlist, GList *msghdr_list,
			gboolean must_sort)
{
  GList *msgs, *l, *mhdr_list=NULL;
  GtkCList *clist;
  t_msgheaders *mh;
  grn_threadlist_stats *tls, *tls1;
  g_return_if_fail(threadlist != NULL);
  g_return_if_fail(msghdr_list != NULL);
  
  grn_lock();
  clist = GTK_CLIST(gtk_object_get_data(GTK_OBJECT(threadlist), KEY_CT_MSGS));
  grn_unlock();
  if (! clist)  return;
  grn_widget_set_style(GTK_WIDGET(clist), grn_prefs.fonts[3], NULL, NULL, NULL);

  for (l=msghdr_list; l; l=l->next)
  {
    t_msgheaders *mh = (t_msgheaders *) l->data;
    t_msgheaders *mhc;
    if (! mh)  continue;
    mhc = t_msgheaders_copy(mh);
    mhdr_list = g_list_prepend(mhdr_list, mhc);
  }
  mhdr_list = g_list_reverse(mhdr_list);
  
  msgs = thread_messages(mhdr_list, must_sort);
  if (! msgs)
  {
    msghdrlist_free(&mhdr_list);
    return;
  }
  
  mh = (t_msgheaders *) mhdr_list->data;
  if (mh && mh->grp && mh->grp->plain_unread)
  {
    g_list_free(mh->grp->plain_unread);
    mh->grp->plain_unread = NULL;
  }
  
  grn_appbar_push(GRN->appbar, FALSE, _("Eliminating read articles..."));
  tls = g_new(grn_threadlist_stats, 1);
  get_tl_stats(msgs, tls);
  if (! grn_prefs.tl_show_read)  msgs = leave_unread(msgs);
  tls->nthunread = g_list_length(msgs);
  grn_lock();
  tls1 = gtk_object_get_data(GTK_OBJECT(threadlist), KEY_TL_STATS);
  if (tls1)  g_free(tls1);
  gtk_object_remove_data(GTK_OBJECT(threadlist), KEY_TL_STATS);
  gtk_object_set_data(GTK_OBJECT(threadlist), KEY_TL_STATS, tls);
  grn_unlock();

  if (! grn_prefs.tl_show_read)  grn_appbar_pop(GRN->appbar);

  if (grn_prefs.thrd_tl_type > 0)  msgs = sort_msglist_threads(msgs);

  grn_lock();
  gtk_clist_freeze(clist);
  gtk_clist_clear(clist);
  grn_unlock();
  
  build_msgs_tree_recursive(GTK_CTREE(clist), NULL, msgs);
  if (grn_prefs.tl_ac)  collapse_root_nodes(clist);
  show_threads_nchildren(clist);
  
  grn_lock();
  gtk_clist_columns_autosize(clist);
  gtk_clist_thaw(clist);
  grn_unlock();
  show_tl_stats(tls);

  grn_lock();
  l = gtk_object_get_data(GTK_OBJECT(threadlist), KEY_MSGLIST);
  if (l)  msglist_free(&l);
  gtk_object_remove_data(GTK_OBJECT(threadlist), KEY_MSGLIST);
  gtk_object_set_data(GTK_OBJECT(threadlist), KEY_MSGLIST, msgs);
  msghdrlist_free(&mhdr_list);
  gtk_widget_grab_focus(GTK_WIDGET(clist));
  grn_unlock();
}


GtkWidget *threadlist_create()
{
  gchar *ttl[NCOLS] = {_("N"), NULL, NULL, _("Subject"), _("Author"), _("Date"), _("Lines"), _("Size")};
  GtkWidget *vbox, *scrwin, *w;
  GtkCList *clist;
  gshort n;

  grn_lock();  
  vbox = gtk_vbox_new(FALSE, 0);

  clist = GTK_CLIST(gtk_ctree_new_with_titles(NCOLS, 3, ttl));
  gtk_clist_set_column_justification(clist, 0, GTK_JUSTIFY_RIGHT);
  gtk_clist_set_column_justification(clist, 1, GTK_JUSTIFY_RIGHT);
  gtk_clist_set_column_justification(clist, 2, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(clist, 5, GTK_JUSTIFY_RIGHT);
  gtk_clist_set_column_justification(clist, 6, GTK_JUSTIFY_RIGHT);
  gtk_clist_set_column_justification(clist, 7, GTK_JUSTIFY_RIGHT);
  gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[0].button, _("Select threading method"), NULL);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[1].button, _("Sort by score"), NULL);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[2].button, _("Toggle visibility of read messages"), NULL);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[3].button, _("Sort by subject"), NULL);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[4].button, _("Sort by author"), NULL);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[5].button, _("Sort by date"), NULL);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[6].button, _("Sort by lines"), NULL);
  gtk_tooltips_set_tip(GRN->tooltips, clist->column[7].button, _("Sort by size"), NULL);
#if 0
  gtk_ctree_set_line_style(GTK_CTREE(clist), GTK_CTREE_LINES_NONE);
  gtk_ctree_set_expander_style(GTK_CTREE(clist), GTK_CTREE_EXPANDER_TRIANGLE);
#endif  
  n = grn_prefs.srt_tl_col;
  clist->sort_type = GTK_SORT_ASCENDING;
  if (n >= 100)
  {
    clist->sort_type = GTK_SORT_DESCENDING;
    n -= 100;
  }
  gtk_clist_set_sort_column(clist, n);

  w = gtk_pixmap_new(GRN->pixmaps.balls[12].pixmap, GRN->pixmaps.balls[12].mask);
  gtk_clist_set_column_widget(clist, 1, w);
  w = gtk_pixmap_new(GRN->pixmaps.caption_unread.pixmap, GRN->pixmaps.caption_unread.mask);
  gtk_clist_set_column_widget(clist, 2, w);
  
  scrwin = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_container_add(GTK_CONTAINER(scrwin), GTK_WIDGET(clist));
  gtk_box_pack_start(GTK_BOX(vbox), scrwin, TRUE, TRUE, 0);

  gtk_object_set_data(GTK_OBJECT(clist), KEY_SCRWIN, scrwin);
  gtk_object_set_data(GTK_OBJECT(clist), KEY_WND, vbox);
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_CT_MSGS, clist);
  gtk_object_set_data(GTK_OBJECT(vbox), KEY_FOCUS, clist);

  gtk_signal_connect(GTK_OBJECT(vbox), "destroy",
                     GTK_SIGNAL_FUNC(evt_tl_delete), NULL);
  gtk_signal_connect(GTK_OBJECT(vbox), "hide",
                     GTK_SIGNAL_FUNC(evt_tl_hide), NULL);
  gtk_signal_connect(GTK_OBJECT(vbox), "show",
                     GTK_SIGNAL_FUNC(evt_tl_show), NULL);
  gtk_signal_connect(GTK_OBJECT(clist), "key_press_event",
                     GTK_SIGNAL_FUNC(evt_tl_keypress), NULL);
  gtk_signal_connect(GTK_OBJECT(clist), "button_press_event",
                     GTK_SIGNAL_FUNC(evt_tl_btnpress), NULL);
  gtk_signal_connect(GTK_OBJECT(clist), "click_column",
                     GTK_SIGNAL_FUNC(evt_click_col), NULL);
  
  gtk_widget_show_all(vbox);
  grn_unlock();
  enable_findagain_menu(FALSE);
  return vbox;
}


static void msg_calc_nchildren(GList *msglist, guint *nchildren)
{
  GList *l;
  if (! msglist)  return;
  for (l=msglist; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    if (! t_message_is_read(msg))  (*nchildren)++;
    msg_calc_nchildren(msg->threads, nchildren);
  }
}
static t_message *get_thread_parent(GtkCTree *ctree, GtkCTreeNode *node)
{
  GtkCTreeNode *n = node;
  GtkCTreeNode *parent = GTK_CTREE_ROW(node)->parent;
  t_message *m;

  if (GTK_CTREE_ROW(node)->level <= 1)  return NULL;
  while (parent)
  {
    n = parent;
    parent = GTK_CTREE_ROW(n)->parent;
  }
  if (n)
  {
    grn_lock();
    m = (t_message *) gtk_ctree_node_get_row_data(ctree, n);
    grn_unlock();
    return m;
  }
  else  return NULL;
}
static void threadlist_refresh_row(GtkCTree *ctree, t_message *msg)
{
  GtkCTreeNode *node;
  t_message *msg1;
  GdkPixmap *pixmap=NULL;
  GdkBitmap *mask=NULL;
  g_return_if_fail(msg != NULL);
  
  grn_lock();
  node = gtk_ctree_find_by_row_data(ctree, NULL, msg);
  grn_unlock();
  if (! node)  return;
  msg1 = get_thread_parent(ctree, node);
  if (msg1)  threadlist_refresh_row(ctree, msg1);

  if (! t_message_is_read(msg))
  {
    pixmap = GRN->pixmaps.unread_article.pixmap;
    mask = GRN->pixmaps.unread_article.mask;
  }
  grn_lock();
  if (pixmap)  gtk_ctree_node_set_pixmap(ctree, node, 2, pixmap, mask);
  else  gtk_ctree_node_set_text(ctree, node, 2, "");

  gtk_ctree_node_set_text(ctree, node, 0, "");
  grn_unlock();

  if (msg->threads && (! msg1))
  {
    guint nchildren = 0;
    msg->nchildren = 0;

    msg_calc_nchildren(msg->threads, &(msg->nchildren));
    nchildren = msg->nchildren;
    if (! t_message_is_read(msg))  nchildren++;
    if (nchildren > 0)
    {
      gchar buf[100];
      snprintf(buf, 99, "%d", nchildren);
      grn_lock();
      gtk_ctree_node_set_text(ctree, node, 0, buf);
      grn_unlock();
    }
  }
}

static void evt_tl_delete(GtkWidget *w, gpointer data)
{
  GList *msgs;
  grn_threadlist_stats *tls1;
  
  msgs = gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_MSGLIST);
  if (msgs)  msglist_free(&msgs);
  gtk_object_remove_data(GTK_OBJECT(GRN->threadlist), KEY_MSGLIST);

  tls1 = gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_TL_STATS);
  if (tls1)  g_free(tls1);
  gtk_object_remove_data(GTK_OBJECT(GRN->threadlist), KEY_TL_STATS);

  GRN->threadlist = NULL;
}

static void evt_tl_hide(GtkWidget *w, gpointer data)
{
  show_threadlist_menu(FALSE);
  grn_appbar_set_status(GRN->appbar, "");
}

static void evt_tl_show(GtkWidget *w, gpointer data)
{
  show_threadlist_menu(TRUE);
  grnq_add_threadlist_recalc(w);
}


static void evt_click_col(GtkCList *clist, int n)
{
  if (flags.open_group)  return;
  if (n == 0)
  {
    gchar *modes[] = {_("nothing"), _("references"), _("subject"), _("ref+subj")};
    gchar *m_now, *m_next, *s;
    guint i;

    grn_prefs.thrd_tl_type++;
    if (grn_prefs.thrd_tl_type > 3)  grn_prefs.thrd_tl_type = 0;
    m_now = modes[grn_prefs.thrd_tl_type];
    i = grn_prefs.thrd_tl_type+1;  m_next = modes[(i>3)?0:i];
    s = g_strdup_printf(_("Thread by %s; (now: by %s)"), m_next, m_now);
    gtk_tooltips_set_tip(GRN->tooltips, clist->column[0].button, s, NULL);
    g_free(s);
    grnq_add_draw_msglist();
    return;
  }
  if (n == 2)
  {
    grn_prefs.tl_show_read = grn_prefs.tl_show_read ? FALSE : TRUE;
    grnq_add_draw_msglist1();
    return;
  }
  if (clist->sort_column == n)
  {
    if (clist->sort_type == GTK_SORT_ASCENDING)
      clist->sort_type = GTK_SORT_DESCENDING;
    else  clist->sort_type = GTK_SORT_ASCENDING;
  }
  else  clist->sort_type = GTK_SORT_ASCENDING;
  
  grn_prefs.srt_tl_col = n;
  if (clist->sort_type == GTK_SORT_DESCENDING)  grn_prefs.srt_tl_col += 100;
  gtk_clist_set_sort_column(clist, n);

  grnq_add_draw_msglist();
}


static void select_cur_node(GtkWidget *w, GtkCTreeNode *node)
{
  GtkCList *clist = GTK_CLIST(w);
  g_return_if_fail(w != NULL);
  g_return_if_fail(GTK_IS_CTREE(w));
  if (! node)  return;

  clist->focus_row = g_list_position(clist->row_list, (GList *) node);
  grn_lock();
  gtk_ctree_select(GTK_CTREE(w), node);
  if (gtk_clist_row_is_visible(clist, clist->focus_row) != GTK_VISIBILITY_FULL)
    gtk_clist_moveto(clist, clist->focus_row, 0, 0,0);
  grn_unlock();
}

static void collapse_cur_thread(GtkWidget *w)
{
  GtkCList *clist = GTK_CLIST(w);
  GtkCTreeRow *row;
  g_return_if_fail(w != NULL);
  g_return_if_fail(GTK_IS_CTREE(w));
  
  row = GTK_CTREE_ROW(g_list_nth(clist->row_list, clist->focus_row));
  if (row->level > 1)
  {
    GtkCTreeNode *node = GTK_CTREE_NODE(row);
    GtkCTreeNode *parent = row->parent;

    while (parent)
    {
      node = parent;
      parent = GTK_CTREE_ROW(node)->parent;
    }
    select_cur_node(w, node);
    gtk_ctree_collapse(GTK_CTREE(w), node);
  }
  else  gtk_ctree_collapse(GTK_CTREE(w), GTK_CTREE_NODE(g_list_nth(clist->row_list, clist->focus_row)));
}

static void expand_thread(GtkCTree *ctree, GtkCTreeNode *node)
{
  GtkCTreeNode *parent = node, *prev = node;
  
  while (parent)
  {
    prev = parent;
    parent = GTK_CTREE_ROW(parent)->parent;
  }
  if (! GTK_CTREE_ROW(prev)->is_leaf)  gtk_ctree_expand_recursive(ctree, prev);
}

t_message *tl_get_selected_message(GtkCTree *ctree)
{
  GtkCTreeNode *node = GTK_CTREE_NODE(g_list_nth(GTK_CLIST(ctree)->row_list, GTK_CLIST(ctree)->focus_row));
  if (node)  return  (t_message *) gtk_ctree_node_get_row_data(ctree, node);
  else  return NULL;
}

void tl_select_message(GtkCTree *ctree, t_message *cur_msg, t_message *msg)
{
  GtkCTreeNode *node = gtk_ctree_find_by_row_data(ctree, NULL, msg);
  t_message *msg_thread, *cur_thread;
  GList *msglist = gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_MSGLIST);
  g_return_if_fail(ctree != NULL);
  if (! node)  return;
  if (! msglist)  return;
  g_return_if_fail(cur_msg != NULL);
  g_return_if_fail(msg != NULL);

  msg_thread = msglist_find_cur_thread(msglist, msg);
  cur_thread = msglist_find_cur_thread(msglist, cur_msg);

  gtk_clist_freeze(GTK_CLIST(ctree));
  if (msg_thread != cur_thread)
    if (grn_prefs.tl_ac)  collapse_cur_thread(GTK_WIDGET(ctree));

  expand_thread(ctree, node);
  select_cur_node(GTK_WIDGET(ctree), node);
  if (gtk_clist_row_is_visible(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row) != GTK_VISIBILITY_FULL)
    gtk_clist_moveto(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row, 0, 0,0);
  gtk_clist_thaw(GTK_CLIST(ctree));
}

t_message *tl_get_root_message(GtkCTree *ctree, t_message *msg)
{
  GtkCTreeNode *node;

  grn_lock();
  node = gtk_ctree_find_by_row_data(ctree, NULL, msg);
  grn_unlock();
  if (! node)  return NULL;
  return get_thread_parent(ctree, node);
}

static void select_prev_next_unread(GtkWidget *w, GList *msglist,
            t_message *cur_msg, gboolean prev, gboolean is_thread, gboolean beg)
{
  t_message *msg, *cur_thread;
  GtkCTreeNode *node;
  if ((! msglist) || (! cur_msg) || (! w))  return;
  cur_thread = msglist_find_cur_thread(msglist, cur_msg);

  if (is_thread)
  {
    if (prev)  msg = msglist_find_prev_thread_unread(msglist, cur_msg);
    else  msg = msglist_find_next_thread_unread(msglist, cur_msg);
  }
  else {
    if (prev)  msg = msglist_find_prev_unread(msglist, cur_msg);
    else  msg = msglist_find_next_unread(msglist, cur_msg);
  }
  node = gtk_ctree_find_by_row_data(GTK_CTREE(w), NULL, msg);
  if (node)
  {
    t_message *msg_thread = msglist_find_cur_thread(msglist, msg);

    gtk_clist_freeze(GTK_CLIST(w));
    if (msg_thread != cur_thread)
      if (grn_prefs.tl_ac)  collapse_cur_thread(w);
    if (beg)
    {
      GtkCTreeNode *n = gtk_ctree_find_by_row_data(GTK_CTREE(w), NULL,
                          get_thread_parent(GTK_CTREE(w), node));
      if (n)  select_cur_node(w, n);
      else  select_cur_node(w, node);
    }
    else {
      expand_thread(GTK_CTREE(w), node);
      select_cur_node(w, node);
    }
    if (gtk_clist_row_is_visible(GTK_CLIST(w), GTK_CLIST(w)->focus_row) != GTK_VISIBILITY_FULL)
      gtk_clist_moveto(GTK_CLIST(w), GTK_CLIST(w)->focus_row, 0, 0,0);
    gtk_clist_thaw(GTK_CLIST(w));
  }
}

static GtkCTree *get_ctree(GtkWidget *w)
{
  GtkCTree *ctree;

  g_return_val_if_fail(w != NULL, NULL);
  if (! GTK_IS_CTREE(w))
  {
    ctree = GTK_CTREE(gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_CT_MSGS));
    g_return_val_if_fail(ctree != NULL, NULL);
    g_return_val_if_fail(GTK_IS_CTREE(ctree), NULL);
  }
  else  ctree = GTK_CTREE(w);
  return ctree;
}

extern pthread_mutex_t ogroup_m;
void evt_tl_open_article(GtkWidget *w)
{
  GtkCTree *ctree = get_ctree(w);
  GtkCList *clist;
  t_message *msg;
  g_return_if_fail(ctree != NULL);

  if (flags.open_article || flags.open_group)  return;
  clist = GTK_CLIST(ctree);
  
  pthread_mutex_lock(&ogroup_m);
  msg = tl_get_selected_message(ctree);
  if (msg)
  {
    if (! GRN->msgwin)  GRN->msgwin = msgwin_create();
    grn_lock();
    grn_set_contents(GRN->msgwin);
    grn_unlock();
    if (msg->mh && msg->mh->grp && msg->mh->grp->name)
      grn_set_title(GTK_WINDOW(GRN->window), _("%s - %s, msg #%d"),
                    GRN_TITLE, msg->mh->grp->name, msg->mh->number);
    if (! msg->cached)  grnq_add_download_article(msg);
    grnq_add_draw_article(msg);
    if (msg->mh && msg->mh->grp && msg->mh->grp->seq)
    {
      gchar *seq = grn_news_add_read(msg->mh->grp->seq, msg->mh->number);
      grn_newsgroup_set_seq(msg->mh->grp, seq);
      threadlist_refresh_row(ctree, msg);
    }
  }
  pthread_mutex_unlock(&ogroup_m);
  show_message_menu(TRUE);
}

static void scroll_line(GtkAdjustment *adj, gint offset, gint width)
{
  gfloat value = adj->value + offset;
  if (value > (adj->upper - width + 4))  value = adj->upper - width + 4;
  if (value < adj->lower)  value = adj->lower;
  gtk_adjustment_set_value(adj, value);
  grn_main_iter();
}

static void scroll_smooth(GtkAdjustment *adj, gint n, gint width)
{
  gint i = 0;
  
  if (n > 0)
    while (i <= n)
    {
      gint step = sin(M_PI/n*i) * 23 + 2;
      if (step > (n - i))  step = n - i;
      i += step;
      scroll_line(adj, step, width);
      if (n == i)  break;
    }
  else
    while (i >= n)
    {
      gint step = -sin(M_PI/n*i) * 23 - 2;
      if (step < (n - i))  step = n - i;
      i += step;
      scroll_line(adj, step, width);
      if (n == i)  break;
    }
}

static gint evt_tl_keypress(GtkWidget *w, GdkEventKey *event, gpointer data)
{
  gint offset = 0;
  GList *msglist;
  t_message *cur_msg;
  gboolean rc = TRUE;
  
  msglist = gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_MSGLIST);
  g_return_val_if_fail(event != NULL, FALSE);
  g_return_val_if_fail(w != NULL, FALSE);
  cur_msg = tl_get_selected_message(GTK_CTREE(w));
  
  switch (event->keyval)
  {
    case GDK_Select:
    case GDK_Execute:
    case GDK_Return:
    case GDK_KP_Enter:
      evt_tl_open_article(w);
      break;
    case GDK_Right:
    case GDK_KP_Right:
      if ((event->state & GDK_CONTROL_MASK) || (event->state & GDK_SHIFT_MASK))
        offset = 1;
      else  evt_tl_open_article(w);
      break;
    case GDK_Left:
    case GDK_KP_Left:
      if ((event->state & GDK_CONTROL_MASK) || (event->state & GDK_SHIFT_MASK))
        offset = -1;
      else {
        evt_grouplist(w);
      }
      break;
    case GDK_Up:
      if (event->state & GDK_CONTROL_MASK)
        select_prev_next_unread(w, msglist, cur_msg, TRUE, FALSE, FALSE);
      else if (event->state & GDK_SHIFT_MASK)
        select_prev_next_unread(w, msglist, cur_msg, TRUE, TRUE, FALSE);
      else  rc = FALSE;
      break;
    case GDK_Down:
      if (event->state & GDK_CONTROL_MASK)  // next unread
        select_prev_next_unread(w, msglist, cur_msg, FALSE, FALSE, FALSE);
      else if (event->state & GDK_SHIFT_MASK)  // next [unread] thread
        select_prev_next_unread(w, msglist, cur_msg, FALSE, TRUE, FALSE);
      else  rc = FALSE;
      break;
    default:  rc = FALSE;
  }
  if (! rc)  return FALSE;
  gtk_signal_emit_stop_by_name(GTK_OBJECT(w), "key_press_event");

  if (offset)
  {
    GtkScrolledWindow *scrwin = GTK_SCROLLED_WINDOW(gtk_object_get_data(GTK_OBJECT(w), KEY_SCRWIN));
    if (scrwin)
    {
      GtkAdjustment *adj = gtk_scrolled_window_get_hadjustment(scrwin);
      if (adj)
      {
	gint width, height, n;
	gdk_window_get_size(w->window, &width, &height);
	if (event->state & GDK_CONTROL_MASK)  n = offset * adj->page_increment;
	else  n = offset * adj->step_increment;
	if (grn_prefs.smooth_scrolling)  scroll_smooth(adj, n, width);
	else  scroll_line(adj, n, width);
      }
    }
  }
  return TRUE;
}

static gint evt_tl_btnpress(GtkWidget *w, GdkEventButton *event, gpointer data)
{
  gboolean processed = FALSE;
  g_return_val_if_fail(event != NULL, FALSE);
  g_return_val_if_fail(w != NULL, FALSE);
  
  if ((event->type == GDK_2BUTTON_PRESS) && (event->button == 1))
  {
    evt_tl_open_article(w);
    processed = TRUE;
  }
  if (processed)
  {
    gtk_signal_emit_stop_by_name(GTK_OBJECT(w), "button_press_event");
    return TRUE;
  }
  return FALSE;
}

static void prev_next_unread_wrapper(GtkWidget *w, gboolean prev,
            gboolean thread, gboolean beg)
{
  GtkCTree *ctree = get_ctree(w);
  GList *msglist;
  t_message *cur_msg;
  g_return_if_fail(ctree != NULL);
  
  msglist = gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_MSGLIST);
  cur_msg = tl_get_selected_message(ctree);
  select_prev_next_unread(GTK_WIDGET(ctree), msglist, cur_msg, prev, thread, beg);
}


void evt_tl_urprev_msg(GtkWidget *w)
{ prev_next_unread_wrapper(w, TRUE, FALSE, FALSE); }
void evt_tl_urnext_msg(GtkWidget *w)
{ prev_next_unread_wrapper(w, FALSE, FALSE, FALSE); }

void evt_tl_urprev_thread(GtkWidget *w)
{ prev_next_unread_wrapper(w, TRUE, TRUE, FALSE); }
void evt_tl_urnext_thread(GtkWidget *w)
{ prev_next_unread_wrapper(w, FALSE, TRUE, FALSE); }

void evt_tl_back(GtkWidget *w)
{ evt_grouplist(w); }


void evt_expand_cur_thread(GtkWidget *w)
{
  GtkCTree *ctree = get_ctree(w);
  g_return_if_fail(ctree != NULL);

  gtk_ctree_expand_recursive(ctree, GTK_CTREE_NODE(g_list_nth(
    GTK_CLIST(ctree)->row_list, GTK_CLIST(ctree)->focus_row)));
  if (gtk_clist_row_is_visible(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row) != GTK_VISIBILITY_FULL)
    gtk_clist_moveto(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row, 0, 0,0);
}

void evt_collapse_cur_thread(GtkWidget *w)
{
  GtkCTree *ctree = get_ctree(w);
  g_return_if_fail(ctree != NULL);
  collapse_cur_thread(GTK_WIDGET(ctree));
}

void evt_expand_all_threads(GtkWidget *w)
{
  GList *msgs, *l;
  GtkCTree *ctree = get_ctree(w);
  g_return_if_fail(ctree != NULL);

  msgs = gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_MSGLIST);
  if (! msgs)  return;
  gtk_clist_freeze(GTK_CLIST(ctree));
  for (l=msgs; l; l=l->next)
  {
    GtkCTreeNode *node;
    t_message *msg = (t_message *) l->data;
    if (! msg)  continue;
    node = gtk_ctree_find_by_row_data(ctree, NULL, msg);
    if (node)  gtk_ctree_expand_recursive(ctree, node);
  }
  gtk_clist_thaw(GTK_CLIST(ctree));
  if (gtk_clist_row_is_visible(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row) != GTK_VISIBILITY_FULL)
    gtk_clist_moveto(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row, 0, 0,0);
}

void evt_collapse_all_threads(GtkWidget *w)
{
  GList *msgs, *l;
  GtkCTree *ctree = get_ctree(w);
  g_return_if_fail(ctree != NULL);

  msgs = gtk_object_get_data(GTK_OBJECT(GRN->threadlist), KEY_MSGLIST);
  if (! msgs)  return;
  gtk_clist_freeze(GTK_CLIST(ctree));
  for (l=msgs; l; l=l->next)
  {
    GtkCTreeNode *node;
    t_message *msg = (t_message *) l->data;
    if (! msg)  continue;
    node = gtk_ctree_find_by_row_data(ctree, NULL, msg);
    if (node)  gtk_ctree_collapse_recursive(ctree, node);
  }
  gtk_clist_thaw(GTK_CLIST(ctree));
  if (gtk_clist_row_is_visible(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row) != GTK_VISIBILITY_FULL)
    gtk_clist_moveto(GTK_CLIST(ctree), GTK_CLIST(ctree)->focus_row, 0, 0,0);
}


static void recalc_threadlist_by_ctree(GtkCTree *ctree)
{
  GtkWidget *w;
  if (! ctree)  return;
  
  w = gtk_object_get_data(GTK_OBJECT(ctree), KEY_WND);
  if (w)  grnq_add_threadlist_recalc(w);
}

void evt_tl_mark_read(GtkWidget *w)
{
  GtkCTree *ctree = get_ctree(w);
  GtkCList *clist;
  t_message *msg;
  g_return_if_fail(ctree != NULL);

  clist = GTK_CLIST(ctree);
  msg = tl_get_selected_message(ctree);
  if (msg && msg->mh && msg->mh->grp && msg->mh->grp->seq)
  {
    gchar *seq = grn_news_add_read(msg->mh->grp->seq, msg->mh->number);
    grn_newsgroup_set_seq(msg->mh->grp, seq);
    threadlist_refresh_row(ctree, msg);
    prev_next_unread_wrapper(w, FALSE, FALSE, FALSE);
    recalc_threadlist_by_ctree(ctree);
  }
}

void evt_tl_mark_unread(GtkWidget *w)
{
  GtkCTree *ctree = get_ctree(w);
  GtkCList *clist;
  t_message *msg;
  g_return_if_fail(ctree != NULL);

  clist = GTK_CLIST(ctree);
  msg = tl_get_selected_message(ctree);
  if (msg && msg->mh && msg->mh->grp && msg->mh->grp->seq)
  {
    gchar *seq = grn_news_add_unread(msg->mh->grp->seq, msg->mh->number);
    grn_newsgroup_set_seq(msg->mh->grp, seq);
    threadlist_refresh_row(ctree, msg);
    recalc_threadlist_by_ctree(ctree);
  }
}


static void mark_msglist(GList *msglist, gboolean unread, GtkCTree *ctree)
{
  GList *l;
  if (! msglist)  return;
  
  for (l=msglist; l; l=l->next)
  {
    t_message *msg = (t_message *) l->data;
    gchar *seq;
    if (! msg)  continue;

    if (unread)  seq = grn_news_add_unread(msg->mh->grp->seq, msg->mh->number);
    else  seq = grn_news_add_read(msg->mh->grp->seq, msg->mh->number);
    grn_newsgroup_set_seq(msg->mh->grp, seq);
    threadlist_refresh_row(ctree, msg);
    mark_msglist(msg->threads, unread, ctree);
  }
}

void tl_mark_thread_read(GtkCTree *ctree)
{
  GtkCList *clist;
  t_message *msg;

  clist = GTK_CLIST(ctree);
  
  msg = tl_get_selected_message(ctree);
  if (msg)  mark_msglist(msg->threads, FALSE, ctree);
  if (msg && msg->mh && msg->mh->grp && msg->mh->grp->seq)
  {
    gchar *seq = grn_news_add_read(msg->mh->grp->seq, msg->mh->number);
    grn_newsgroup_set_seq(msg->mh->grp, seq);
    threadlist_refresh_row(ctree, msg);
    recalc_threadlist_by_ctree(ctree);
  }
  prev_next_unread_wrapper(GTK_WIDGET(ctree), FALSE, FALSE, TRUE);
}

static void catchup_cb(gint reply, GtkCTree *ctree)
{
  g_return_if_fail(ctree != NULL);
  if (reply == GNOME_YES)  tl_mark_thread_read(ctree);
}
void evt_tl_mark_thread_read(GtkWidget *w)
{
  GtkCTree *ctree = get_ctree(w);
  g_return_if_fail(ctree != NULL);

  if (grn_prefs.cfms[2])
    gnome_app_question(GNOME_APP(GRN->window), _("Do you really wish to catchup current thread?"),
      (GnomeReplyCallback) catchup_cb, ctree);
  else  tl_mark_thread_read(ctree);
}

void tl_mark_thread_unread(GtkCTree *ctree)
{
  GtkCList *clist;
  t_message *msg;

  clist = GTK_CLIST(ctree);
  
  msg = tl_get_selected_message(ctree);
  if (msg)  mark_msglist(msg->threads, TRUE, ctree);
  if (msg && msg->mh && msg->mh->grp && msg->mh->grp->seq)
  {
    gchar *seq = grn_news_add_unread(msg->mh->grp->seq, msg->mh->number);
    grn_newsgroup_set_seq(msg->mh->grp, seq);
    threadlist_refresh_row(ctree, msg);
    recalc_threadlist_by_ctree(ctree);
  }
}

static void unread_cb(gint reply, GtkCTree *ctree)
{
  g_return_if_fail(ctree != NULL);
  if (reply == GNOME_YES)  tl_mark_thread_unread(ctree);
}
void evt_tl_mark_thread_unread(GtkWidget *w)
{
  GtkCTree *ctree = get_ctree(w);
  g_return_if_fail(ctree != NULL);

  if (grn_prefs.cfms[3])
    gnome_app_question(GNOME_APP(GRN->window), _("Do you really wish to mark current thread unread?"),
      (GnomeReplyCallback) unread_cb, ctree);
  else  tl_mark_thread_unread(ctree);
}


syntax highlighted by Code2HTML, v. 0.9.1