Logo Search packages:      
Sourcecode: balsa version File versions

balsa-mblist.c

/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
/* Balsa E-Mail Client
 * Copyright (C) 1997-2003 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.
 */

#include "config.h"

#include <gnome.h>
/* #include <gtk/gtkfeatures.h> */
#include <string.h>
#include <gdk/gdkfont.h>

#include "balsa-app.h"
#include "balsa-icons.h"
#include "balsa-index.h"
#include "balsa-mblist.h"
#include "libbalsa.h"
#include "i18n.h"
#include "mailbox-node.h"
#include "main-window.h"

/* Column numbers (used for sort_column_id): */
typedef enum {
    BMBL_TREE_COLUMN_NAME = 1,
    BMBL_TREE_COLUMN_UNREAD,
    BMBL_TREE_COLUMN_TOTAL
} BmblTreeColumnId;

/* object arguments */
enum {
    PROP_0,
    PROP_SHOW_CONTENT_INFO
};

/* Drag and Drop stuff */
enum {
    TARGET_MESSAGES
};

/* tree model columns */
enum {
    MBNODE_COLUMN = 0,         /* we use 0 in other code */
    ICON_COLUMN,
    NAME_COLUMN,
    WEIGHT_COLUMN,
    STYLE_COLUMN,
    UNREAD_COLUMN,
    TOTAL_COLUMN,
    N_COLUMNS
};

static GtkTargetEntry bmbl_drop_types[] = {
    {"x-application/x-message-list", GTK_TARGET_SAME_APP, TARGET_MESSAGES}
};

static GtkTreeViewClass *parent_class = NULL;

/* class methods */
static void bmbl_class_init(BalsaMBListClass * klass);
static void bmbl_set_property(GObject * object, guint prop_id,
                              const GValue * value, GParamSpec * pspec);
static void bmbl_get_property(GObject * object, guint prop_id,
                              GValue * value, GParamSpec * pspec);
static gboolean bmbl_drag_motion(GtkWidget * mblist,
                                 GdkDragContext * context, gint x, gint y,
                                 guint time);
static gboolean bmbl_popup_menu(GtkWidget * widget);
static void bmbl_select_mailbox(GtkTreeSelection * selection,
                                gpointer data);
static void bmbl_init(BalsaMBList * mblist);
static gboolean bmbl_selection_func(GtkTreeSelection * selection,
                                    GtkTreeModel * model,
                                    GtkTreePath * path,
                                    gboolean path_currently_selected,
                                    gpointer data);

/* callbacks */
static void bmbl_tree_expand(GtkTreeView * tree_view, GtkTreeIter * iter,
                             GtkTreePath * path, gpointer data);
static void bmbl_tree_collapse(GtkTreeView * tree_view, GtkTreeIter * iter,
                               GtkTreePath * path, gpointer data);
static gint bmbl_row_compare(GtkTreeModel * model,
                             GtkTreeIter * iter1,
                             GtkTreeIter * iter2, gpointer data);
static gboolean bmbl_button_press_cb(GtkWidget * widget,
                                     GdkEventButton * event,
                                     gpointer data);
static void bmbl_column_resize(GtkWidget * widget,
                               GtkAllocation * allocation, gpointer data);
static void bmbl_drag_cb(GtkWidget * widget, GdkDragContext * context,
                         gint x, gint y,
                         GtkSelectionData * selection_data, guint info,
                         guint32 time, gpointer data);
static void bmbl_row_activated_cb(GtkTreeView * tree_view,
                                  GtkTreePath * path,
                                  GtkTreeViewColumn * column,
                                  gpointer data);
static void bmbl_mailbox_changed_cb(LibBalsaMailbox * mailbox,
                            gpointer data);
/* helpers */
static gboolean bmbl_find_all_unread_mboxes_func(GtkTreeModel * model,
                                                 GtkTreePath * path,
                                                 GtkTreeIter * iter,
                                                 gpointer data);
static void bmbl_real_disconnect_mbnode_signals(BalsaMailboxNode * mbnode,
                                    GtkTreeModel * model);
static gboolean bmbl_store_redraw_mbnode(GtkTreeIter * iter,
                               BalsaMailboxNode * mbnode);
static void bmbl_node_style(GtkTreeModel * model, GtkTreeIter * iter,
                      gint total_messages);
static gint bmbl_core_mailbox(LibBalsaMailbox * mailbox);
static void bmbl_do_popup(GtkTreeView * tree_view, GtkTreePath * path,
                          GdkEventButton * event);
static void bmbl_expand_to_row(BalsaMBList * mblist, GtkTreePath * path);
/* end of prototypes */

/* class methods */

GtkType
balsa_mblist_get_type(void)
{
    static GtkType mblist_type = 0;

    if (!mblist_type) {
      static const GTypeInfo mblist_info = {
          sizeof(BalsaMBListClass),
            NULL,               /* base_init */
            NULL,               /* base_finalize */
          (GClassInitFunc) bmbl_class_init,
            NULL,               /* class_finalize */
            NULL,               /* class_data */
          sizeof(BalsaMBList),
            0,                  /* n_preallocs */
          (GInstanceInitFunc) bmbl_init
      };

      mblist_type =
            g_type_register_static(GTK_TYPE_TREE_VIEW,
                                 "BalsaMBList",
                                   &mblist_info, 0);
    }

    return mblist_type;
}


static void
bmbl_class_init(BalsaMBListClass * klass)
{
    GObjectClass *o_class;
    GtkObjectClass *object_class;
    GtkWidgetClass *widget_class;

    parent_class = g_type_class_peek_parent(klass);

    o_class = (GObjectClass *) klass;
    object_class = (GtkObjectClass *) klass;
    widget_class = (GtkWidgetClass *) klass;

    /* GObject signals */
    o_class->set_property = bmbl_set_property;
    o_class->get_property = bmbl_get_property;

    /* GtkWidget signals */
    widget_class->drag_motion = bmbl_drag_motion;
    widget_class->popup_menu = bmbl_popup_menu;

    /* Properties */
    g_object_class_install_property(o_class, PROP_SHOW_CONTENT_INFO,
                                    g_param_spec_boolean
                                    ("show_content_info", NULL, NULL,
                                     FALSE, G_PARAM_READWRITE));
}

static void
bmbl_set_property(GObject * object, guint prop_id,
                          const GValue * value, GParamSpec * pspec)
{
    BalsaMBList *mblist = BALSA_MBLIST(object);
    GtkTreeView *tree_view = GTK_TREE_VIEW(object);
    gboolean display_info;
    GtkTreeViewColumn *column;

    switch (prop_id) {
    case PROP_SHOW_CONTENT_INFO:
        display_info = g_value_get_boolean(value);
        mblist->display_info = display_info;
        column = gtk_tree_view_get_column(tree_view, 1);
        gtk_tree_view_column_set_visible(column, display_info);
        column = gtk_tree_view_get_column(tree_view, 2);
        gtk_tree_view_column_set_visible(column, display_info);
        break;

    default:
        break;
    }
}

static void
bmbl_get_property(GObject * object, guint prop_id, GValue * value,
                          GParamSpec * pspec)
{
    BalsaMBList *mblist = BALSA_MBLIST(object);

    switch (prop_id) {
    case PROP_SHOW_CONTENT_INFO:
        g_value_set_boolean(value, mblist->display_info);
        break;
    default:
        break;
    }
}

static gboolean
bmbl_drag_motion(GtkWidget * mblist, GdkDragContext * context, gint x,
                 gint y, guint time)
{
    GtkTreeView *tree_view = GTK_TREE_VIEW(mblist);
    GtkTreePath *path;
    GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
    GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
    gboolean ret_val;
    gboolean can_drop;

    ret_val =
        GTK_WIDGET_CLASS(parent_class)->drag_motion(mblist, context, x, y,
                                                    time);

    gtk_tree_view_get_drag_dest_row(tree_view, &path, NULL);
    if (!path)
        return FALSE;

    can_drop = bmbl_selection_func(selection, model, path, FALSE, NULL);
    gtk_tree_view_set_drag_dest_row(tree_view, can_drop ? path : NULL,
                                    GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
    gtk_tree_path_free(path);

    gdk_drag_status(context,
                    (context->actions ==
                     GDK_ACTION_COPY) ? GDK_ACTION_COPY :
                    GDK_ACTION_MOVE, time);

    return (ret_val && can_drop);
}

/*
 * Set up the mail box list, including the tree's appearance and the
 * callbacks
 */
static void
bmbl_init(BalsaMBList * mblist)
{
    GtkTreeStore *store = balsa_mblist_get_store();
    GtkTreeView *tree_view = GTK_TREE_VIEW(mblist);
    GtkCellRenderer *renderer;
    GtkTreeViewColumn *column;

    gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(store));
    g_object_unref(store);

    /* Mailbox icon and name go in first column. */
    column = gtk_tree_view_column_new();
    gtk_tree_view_column_set_title(column, _("Mailbox"));
    gtk_tree_view_column_set_alignment(column, 0.5);
    renderer = gtk_cell_renderer_pixbuf_new();
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_set_attributes(column, renderer,
                                        "pixbuf", ICON_COLUMN,
                                        NULL);
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_set_attributes(column, renderer,
                                        "text", NAME_COLUMN,
                                        "weight", WEIGHT_COLUMN,
                                        "style", STYLE_COLUMN,
                                        NULL);
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
    gtk_tree_view_column_set_fixed_width(column,
                                         balsa_app.mblist_name_width);
    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_append_column(tree_view, column);
    gtk_tree_view_column_set_sort_column_id(column,
                                            BMBL_TREE_COLUMN_NAME);
     /* set sort column id will make the column clickable - disable that! */
    gtk_tree_view_column_set_clickable(column, FALSE);

    /* Message counts are right-justified, each in a column centered
     * under its heading. */
    /* Unread message count column */
    column = gtk_tree_view_column_new();
    gtk_tree_view_column_set_title(column, "U");
    gtk_tree_view_column_set_alignment(column, 0.5);
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    renderer = gtk_cell_renderer_text_new();
    g_object_set(renderer, "xalign", 1.0, NULL);
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_set_attributes(column, renderer,
                                        "text", UNREAD_COLUMN,
                                        NULL);
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
    gtk_tree_view_column_set_fixed_width(column,
                                         balsa_app.mblist_newmsg_width);
    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_column_set_visible(column, mblist->display_info);
    gtk_tree_view_append_column(tree_view, column);
#ifdef SORTING_MAILBOX_LIST_IS_USEFUL
    gtk_tree_view_column_set_sort_column_id(column,
                                            BMBL_TREE_COLUMN_UNREAD);
#endif

    /* Total message count column */
    column = gtk_tree_view_column_new();
    gtk_tree_view_column_set_title(column, "T");
    gtk_tree_view_column_set_alignment(column, 0.5);
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    renderer = gtk_cell_renderer_text_new();
    g_object_set(renderer, "xalign", 1.0, NULL);
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_set_attributes(column, renderer,
                                        "text", TOTAL_COLUMN,
                                        NULL);
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
    gtk_tree_view_column_set_fixed_width(column,
                                         balsa_app.mblist_totalmsg_width);
    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_column_set_visible(column, mblist->display_info);
    gtk_tree_view_append_column(tree_view, column);
#ifdef SORTING_MAILBOX_LIST_IS_USEFUL
    gtk_tree_view_column_set_sort_column_id(column,
                                            BMBL_TREE_COLUMN_TOTAL);
#endif
    /* arrange for non-mailbox nodes to be non-selectable */
    gtk_tree_selection_set_select_function(gtk_tree_view_get_selection
                                           (tree_view),
                                           bmbl_selection_func, NULL,
                                           NULL);

    g_signal_connect_after(G_OBJECT(tree_view), "row-expanded",
                           G_CALLBACK(bmbl_tree_expand), NULL);
    g_signal_connect(G_OBJECT(tree_view), "row-collapsed",
                     G_CALLBACK(bmbl_tree_collapse), NULL);

    g_object_set(G_OBJECT(mblist),
                 "show_content_info",
                 balsa_app.mblist_show_mb_content_info,
                 NULL);

    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store),
                                    BMBL_TREE_COLUMN_NAME,
                                    bmbl_row_compare,
                                    GINT_TO_POINTER(BMBL_TREE_COLUMN_NAME),
                                    NULL);
#ifdef SORTING_MAILBOX_LIST_IS_USEFUL
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store),
                                    BMBL_TREE_COLUMN_UNREAD,
                                    bmbl_row_compare,
                                    GINT_TO_POINTER(BMBL_TREE_COLUMN_UNREAD),
                                    NULL);
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store),
                                    BMBL_TREE_COLUMN_TOTAL,
                                    bmbl_row_compare,
                                    GINT_TO_POINTER(BMBL_TREE_COLUMN_TOTAL),
                                    NULL);
#endif
    /* Default is ascending sort by name */
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
                               BMBL_TREE_COLUMN_NAME,
                               GTK_SORT_ASCENDING);
}

/*
 * balsa_mblist_get_store
 *
 * Return balsa_app.mblist_tree_store, setting it up if this is the
 * first time.
 */
GtkTreeStore *
balsa_mblist_get_store(void)
{
    if (balsa_app.mblist_tree_store)
      g_object_ref(balsa_app.mblist_tree_store);
    else {
        balsa_app.mblist_tree_store =
            gtk_tree_store_new(N_COLUMNS,
                               G_TYPE_OBJECT,     /* MBNODE_COLUMN */
                               GDK_TYPE_PIXBUF,   /* ICON_COLUMN   */
                               G_TYPE_STRING,     /* NAME_COLUMN   */
                               PANGO_TYPE_WEIGHT, /* WEIGHT_COLUMN */
                               PANGO_TYPE_STYLE,  /* STYLE_COLUMN */
                               G_TYPE_STRING,     /* UNREAD_COLUMN */
                               G_TYPE_STRING      /* TOTAL_COLUMN  */
            );
        g_object_add_weak_pointer(G_OBJECT(balsa_app.mblist_tree_store),
                                  (gpointer) & balsa_app.
                                  mblist_tree_store);
    }

    return balsa_app.mblist_tree_store;
}

/* 
 * bmbl_selection_func
 *
 * Used to filter whether or not a row may be selected.
 */
static gboolean
bmbl_selection_func(GtkTreeSelection * selection, GtkTreeModel * model,
                      GtkTreePath * path, gboolean path_currently_selected,
                      gpointer data)
{
    GtkTreeIter iter;
    BalsaMailboxNode *mbnode;
    gboolean retval;

    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);

    /* If the node is selected, allow it to be deselected, whether or
     * not it has a mailbox (if it doesn't, it shouldn't have been
     * selected in the first place, but you never know...). */
    retval = (path_currently_selected || (mbnode && mbnode->mailbox));
    g_object_unref(mbnode);
    return retval;
}

GtkWidget *
balsa_mblist_new()
{
    BalsaMBList *new;

    new = g_object_new(balsa_mblist_get_type(), NULL);
    
    return GTK_WIDGET(new);
}

/* callbacks */

/* "row-expanded" */

static void
bmbl_tree_expand(GtkTreeView * tree_view, GtkTreeIter * iter,
                    GtkTreePath * path, gpointer data)
{
    GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
    BalsaMailboxNode *mbnode;
    GtkTreeIter child_iter;

    gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
    balsa_mailbox_node_scan_children(mbnode);

    if (!mbnode->mailbox)
        gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                           ICON_COLUMN,   
                           gtk_widget_render_icon
                           (GTK_WIDGET(balsa_app.main_window),
                            BALSA_PIXMAP_MBOX_DIR_OPEN,
                            GTK_ICON_SIZE_MENU, NULL),
                           -1);
    g_object_unref(mbnode);

    if (gtk_tree_model_iter_children(model, &child_iter, iter)) {
      GtkWidget *current_index =
          balsa_window_find_current_index(balsa_app.main_window);
      LibBalsaMailbox *current_mailbox =
          current_index ?
          BALSA_INDEX(current_index)->mailbox_node->mailbox :
          NULL;
      gboolean first_mailbox = TRUE;

        do {
            gtk_tree_model_get(model, &child_iter,
                               MBNODE_COLUMN, &mbnode, -1);
            if (mbnode && mbnode->mailbox) {
            /* Mark only one mailbox as exposed. */
            if (first_mailbox) {
                libbalsa_mailbox_set_exposed(mbnode->mailbox, TRUE);
                first_mailbox = FALSE;
            } else
                libbalsa_mailbox_set_exposed(mbnode->mailbox, FALSE);
            if (mbnode->mailbox == current_mailbox) {
                GtkTreeSelection *selection =
                  gtk_tree_view_get_selection(tree_view);
                g_signal_handlers_block_by_func(selection,
                                        bmbl_select_mailbox,
                                        NULL);
                gtk_tree_selection_select_iter(selection, &child_iter);
                g_signal_handlers_unblock_by_func(selection,
                                          bmbl_select_mailbox,
                                          NULL);
            }
          }
          g_object_unref(mbnode);
        } while (gtk_tree_model_iter_next(model, &child_iter));
    }
}

/* "row-collapsed" */
static void
bmbl_tree_collapse_helper(GtkTreeModel * model, GtkTreeIter * iter)
{
    GtkTreeIter child_iter;

    if (gtk_tree_model_iter_children(model, &child_iter, iter)) {
        do {
            BalsaMailboxNode *mbnode;

            gtk_tree_model_get(model, &child_iter,
                               MBNODE_COLUMN, &mbnode, -1);
            if (mbnode->mailbox)
            libbalsa_mailbox_set_exposed(mbnode->mailbox, FALSE);
          g_object_unref(mbnode);
            bmbl_tree_collapse_helper(model, &child_iter);
        } while (gtk_tree_model_iter_next(model, &child_iter));
    }
}

static void
bmbl_tree_collapse(GtkTreeView * tree_view, GtkTreeIter * iter,
                      GtkTreePath * path, gpointer data)
{
    GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
    BalsaMailboxNode *mbnode;

    gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);

    if (!mbnode->mailbox)
        gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                           ICON_COLUMN,   
                           gtk_widget_render_icon
                           (GTK_WIDGET(balsa_app.main_window),
                            BALSA_PIXMAP_MBOX_DIR_CLOSED,
                            GTK_ICON_SIZE_MENU, NULL),
                           -1);
    g_object_unref(mbnode);

    bmbl_tree_collapse_helper(model, iter);
}

/* bmbl_row_compare
 * 
 * This function determines the sorting order of the list, depending
 * on what column is selected.  The first column sorts by name, with
 * exception given to the five "core" mailboxes (Inbox, Draftbox,
 * Sentbox, Outbox, Trash).  The second sorts by number of unread
 * messages, and the third by total number of messages.
 * */
static gint
bmbl_row_compare(GtkTreeModel * model, GtkTreeIter * iter1,
                 GtkTreeIter * iter2, gpointer data)
{
    BmblTreeColumnId sort_column = GPOINTER_TO_INT(data);
    BalsaMailboxNode *mbnode;
    LibBalsaMailbox *m1 = NULL;
    LibBalsaMailbox *m2 = NULL;
    gchar *name1, *name2;
    gint core1;
    gint core2;
    gint ret_val = 0;

    gtk_tree_model_get(model, iter1,
                       MBNODE_COLUMN, &mbnode, NAME_COLUMN, &name1, -1);
    m1 = mbnode->mailbox;
    g_object_unref(mbnode);

    gtk_tree_model_get(model, iter2,
                       MBNODE_COLUMN, &mbnode, NAME_COLUMN, &name2, -1);
    m2 = mbnode->mailbox;
    g_object_unref(mbnode);

    switch (sort_column) {
    case BMBL_TREE_COLUMN_NAME:
        /* compare using names, potentially mailboxnodes */
        core1 = bmbl_core_mailbox(m1);
        core2 = bmbl_core_mailbox(m2);
        ret_val = ((core1 || core2) ? core2 - core1
                   : g_ascii_strcasecmp(name1, name2));
        break;

    case BMBL_TREE_COLUMN_UNREAD:
        ret_val = ((m1 ? m1->unread_messages : 0)
                   - (m2 ? m2->unread_messages : 0));
        break;

    case BMBL_TREE_COLUMN_TOTAL:
        ret_val = ((m1 ? libbalsa_mailbox_total_messages(m1) : 0)
                   - (m2 ? libbalsa_mailbox_total_messages(m2) : 0));
        break;
    }

    g_free(name1);
    g_free(name2);
    return ret_val;
}

/* bmbl_button_press_cb:
   handle mouse button press events that occur on mailboxes
   (clicking on folders is passed to GtkTreeView and may trigger expand events
*/
static gboolean
bmbl_button_press_cb(GtkWidget * widget, GdkEventButton * event,
                     gpointer data)
{
    GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
    GtkTreePath *path;

    if (event->type != GDK_BUTTON_PRESS || event->button != 3
        || event->window != gtk_tree_view_get_bin_window(tree_view))
        return FALSE;

    if (!gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y,
                                       &path, NULL, NULL, NULL))
        path = NULL;
    bmbl_do_popup(tree_view, path, event);
    /* bmbl_do_popup frees path */

    return TRUE;
}

/* bmbl_popup_menu:
 * default handler for the "popup-menu" signal, which is issued when the
 * user hits shift-F10
 */
static gboolean
bmbl_popup_menu(GtkWidget * widget)
{
    GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
    GtkTreePath *path;

    gtk_tree_view_get_cursor(tree_view, &path, NULL);
    bmbl_do_popup(tree_view, path, NULL);
    /* bmbl_do_popup frees path */
    return TRUE;
}

/* bmbl_do_popup:
 * do the popup, and free the path
 */
static void
bmbl_do_popup(GtkTreeView * tree_view, GtkTreePath * path,
              GdkEventButton * event)
{
    BalsaMailboxNode *mbnode = NULL;
    gint event_button;
    guint event_time;
    GtkWidget *menu;

    if (path) {
        GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
        GtkTreeIter iter;

        if (gtk_tree_model_get_iter(model, &iter, path))
            gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
        gtk_tree_path_free(path);
    }

    if (event) {
        event_button = event->button;
        event_time = event->time;
    } else {
        event_button = 0;
        event_time = gtk_get_current_event_time();
    }

    menu = balsa_mailbox_node_get_context_menu(mbnode);
    g_object_ref(menu);
    gtk_object_sink(GTK_OBJECT(menu));
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
               event_button, event_time);
    g_object_unref(menu);

    if (mbnode)
      g_object_unref(mbnode);
}

/* bmbl_column_resize [MBG]
 *
 * clist: The clist (in this case ctree), that is having it's columns resized.
 * column: The column being resized
 * size:  The new size of the column
 * data:  The data passed on to the callback when it was connected (NULL)
 *
 * Description: This callback assigns the new column widths to the balsa_app,
 * so they can be saved and restored between sessions.
 * */
static void
bmbl_column_resize(GtkWidget * widget,
                           GtkAllocation * allocation, gpointer data)
{
    GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
    gint name_width =
        gtk_tree_view_column_get_width(gtk_tree_view_get_column
                                       (tree_view, 0));
    gint newmsg_width =
        gtk_tree_view_column_get_width(gtk_tree_view_get_column
                                       (tree_view, 1));
    gint totalmsg_width =
        gtk_tree_view_column_get_width(gtk_tree_view_get_column
                                       (tree_view, 2));

    if (name_width > 0 && newmsg_width > 0 && totalmsg_width > 0) {
        balsa_app.mblist_name_width = name_width;
        balsa_app.mblist_newmsg_width = newmsg_width;
        balsa_app.mblist_totalmsg_width = totalmsg_width;
    }
}

/* bmbl_drag_cb
 * 
 * Description: This is the drag_data_recieved signal handler for the
 * BalsaMBList.  It retrieves the source BalsaIndex and transfers the
 * index's selected messages to the target
 * mailbox.  Depending on what key is held down when the message(s)
 * are dropped they are either copied or moved.  The default action is
 * to copy.
 * */
static void
bmbl_drag_cb(GtkWidget * widget, GdkDragContext * context,
             gint x, gint y, GtkSelectionData * selection_data,
             guint info, guint32 time, gpointer data)
{
    GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
    GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
    GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
    GtkTreePath *path;
    GtkTreeIter iter;
    LibBalsaMailbox *mailbox;
    LibBalsaMailbox *orig_mailbox;
    BalsaIndex *orig_index;

    if (!selection_data || !selection_data->data)
      /* Drag'n'drop is weird... */
      return;

    orig_index = *(BalsaIndex **) selection_data->data;
    if (orig_index->selected->len == 0)
      /* it is actually possible to drag from GtkTreeView when no rows
       * are selected: Disable preview for that. */
       return; 

    orig_mailbox = orig_index->mailbox_node->mailbox;

    /* find the node and mailbox */

    /* we should be able to use:
     * gtk_tree_view_get_drag_dest_row(tree_view, &path, NULL);
     * but it sets path to NULL for some reason, so we'll go down to a
     * lower level. */
    if (gtk_tree_view_get_dest_row_at_pos(tree_view,
                                          x, y, &path, NULL)) {
      BalsaMailboxNode *mbnode;

        gtk_tree_model_get_iter(model, &iter, path);
        gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
        mailbox = mbnode->mailbox;
      g_object_unref(mbnode);

        /* cannot transfer to the originating mailbox */
        if (mailbox != NULL && mailbox != orig_mailbox)
            balsa_index_transfer(orig_index, orig_index->selected, mailbox,
                                 context->action != GDK_ACTION_MOVE);
        gtk_tree_path_free(path);
    }

    if (balsa_find_iter_by_data(&iter, orig_mailbox))
        gtk_tree_selection_select_iter(selection, &iter);
}

/* bmbl_select_mailbox
 *
 * This function is called when the user clicks on the mailbox list,
 * to open the mailbox. It's also called if the user uses the keyboard
 * to focus on the mailbox, in which case we don't open the mailbox.
 */
static void
bmbl_select_mailbox(GtkTreeSelection * selection, gpointer data)
{
    GdkEvent *event = gtk_get_current_event();
    GtkTreeIter iter;
    GtkTreeView *tree_view =
        gtk_tree_selection_get_tree_view(selection);
    GtkTreeModel *model =
        gtk_tree_view_get_model(tree_view);
    GtkTreePath *path;

    if (!event) {
      GtkTreeModel *model;
      GtkTreeIter iter;

      if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
          BalsaMailboxNode *mbnode;
          LibBalsaMailbox *mailbox;
          gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
          mailbox = mbnode->mailbox;
          g_object_unref(mbnode);
          if (MAILBOX_OPEN(mailbox))
            /* Opening a mailbox under program control. */
            return;
      }
      /* Not opening a mailbox--must be the initial selection of the
       * first mailbox in the list, so we'll unselect it again. */
      g_signal_handlers_block_by_func(selection, bmbl_select_mailbox, NULL);
      gtk_tree_selection_unselect_all(selection);
      g_signal_handlers_unblock_by_func(selection, bmbl_select_mailbox, NULL);
        return;
    }
    if (event->type != GDK_BUTTON_PRESS
            /* keyboard navigation */
        || event->button.button != 1
            /* soft select */ ) {
        gdk_event_free(event);
        return;
    }

    if (!gtk_tree_view_get_path_at_pos(tree_view, event->button.x,
                                       event->button.y, &path,
                                       NULL, NULL, NULL)) {
        /* GtkTreeView selects the first node in the tree when the
         * widget first gets the focus, whether it's a keyboard event or
         * a button event. If it's a button event, but no mailbox was
         * clicked, we'll just undo that selection and return. */
      g_signal_handlers_block_by_func(selection, bmbl_select_mailbox, NULL);
        gtk_tree_selection_unselect_all(selection);
      g_signal_handlers_unblock_by_func(selection, bmbl_select_mailbox, NULL);
        gdk_event_free(event);
        return;
    }
    
    if (gtk_tree_selection_path_is_selected(selection, path)) {
        BalsaMailboxNode *mbnode;

        gtk_tree_model_get_iter(model, &iter, path);
        gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
        g_return_if_fail(mbnode != NULL);

        if (mbnode->mailbox)
            balsa_mblist_open_mailbox(mbnode->mailbox);
      g_object_unref(mbnode);
    }
    gtk_tree_path_free(path);
    gdk_event_free(event);
}

/*
 * bmbl_row_activated_cb: callback for the "row-activated" signal
 *
 * This is emitted when focus is on a mailbox, and the user hits the
 * space bar. It's also emitted if the user double-clicks on a mailbox,
 * in which case we've already opened the mailbox. We could detect this
 * by looking at gtk_current_event, but we don't want the extra code.
 */
static void
bmbl_row_activated_cb(GtkTreeView * tree_view, GtkTreePath * path,
                      GtkTreeViewColumn * column, gpointer data)
{
    GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
    GtkTreeIter iter;
    BalsaMailboxNode *mbnode;

    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
    g_return_if_fail(mbnode != NULL);

    if (mbnode->mailbox)
        balsa_mblist_open_mailbox(mbnode->mailbox);
    g_object_unref(mbnode);
}

/* Mailbox status changed callbacks: update the UI in an idle handler.
 */

struct update_mbox_data {
    LibBalsaMailbox *mailbox;
    gint total_messages; /* to be compatible with update_mailbox() arg. */
    gboolean notify;
};
static void bmbl_update_mailbox(GtkTreeStore * store,
                        LibBalsaMailbox * mailbox,
                        gint total_messages);
static gboolean
update_mailbox_idle(struct update_mbox_data *umd)
{
    gdk_threads_enter();
    if (umd->mailbox) {
        g_object_remove_weak_pointer(G_OBJECT(umd->mailbox),
                                     (gpointer) & umd->mailbox);
        if (balsa_app.mblist_tree_store) {
            bmbl_update_mailbox(balsa_app.mblist_tree_store, umd->mailbox,
                                umd->total_messages);
            check_new_messages_count(umd->mailbox, umd->notify);
        }
        g_object_set_data(G_OBJECT(umd->mailbox), "mblist-update", NULL);
    }
    gdk_threads_leave();
    g_free(umd);
    return FALSE;
}

static void
bmbl_mailbox_changed_cb(LibBalsaMailbox * mailbox, gpointer data)
{
    struct update_mbox_data *umd;
    g_return_if_fail(mailbox);
    if( (umd = g_object_get_data(G_OBJECT(mailbox), "mblist-update")) ) {
        if(!MAILBOX_CLOSED(mailbox))
            umd->total_messages = libbalsa_mailbox_total_messages(mailbox);
        return;
    }
    umd = g_new(struct update_mbox_data,1);
    g_object_set_data(G_OBJECT(mailbox), "mblist-update", umd);
    umd->mailbox = mailbox;
    g_object_add_weak_pointer(G_OBJECT(mailbox), (gpointer) &umd->mailbox);
    umd->total_messages = 
        !MAILBOX_CLOSED(mailbox) 
        ? (gint)libbalsa_mailbox_total_messages(mailbox) : -1;
    umd->notify = (mailbox->state == LB_MAILBOX_STATE_OPEN
                   || mailbox->state == LB_MAILBOX_STATE_CLOSED);
    g_idle_add((GSourceFunc)update_mailbox_idle, umd);
}

/* public methods */

/* Caller must unref mbnode. */
BalsaMailboxNode *
balsa_mblist_get_selected_node(BalsaMBList * mbl)
{
    GtkTreeSelection *select =
        gtk_tree_view_get_selection(GTK_TREE_VIEW(mbl));
    BalsaMailboxNode *mbnode = NULL;
    GtkTreeModel *model;
    GtkTreeIter iter;

    if (gtk_tree_selection_get_selected(select, &model, &iter))
        gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);

    return mbnode;
}

/* mblist_find_all_unread_mboxes:
   find all nodes and translate them to mailbox list 
*/
static gboolean
bmbl_find_all_unread_mboxes_func(GtkTreeModel * model,
                                   GtkTreePath * path, GtkTreeIter * iter,
                                   gpointer data)
{
    GList **r = data;
    BalsaMailboxNode *mbnode;

    gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
    if (mbnode->mailbox && mbnode->mailbox->has_unread_messages)
        *r = g_list_prepend(*r, mbnode->mailbox);
    g_object_unref(mbnode);

    return FALSE;
}

GList *
balsa_mblist_find_all_unread_mboxes(void)
{
    GList *res = NULL;
    GtkTreeModel *model = GTK_TREE_MODEL(balsa_app.mblist);

    gtk_tree_model_foreach(model, bmbl_find_all_unread_mboxes_func, &res);

    return res;
}

/* mblist_open_mailbox
 * 
 * Description: This checks to see if the mailbox is already on a different
 * mailbox page, or if a new page needs to be created and the mailbox
 * parsed.
 */
void
balsa_mblist_open_mailbox(LibBalsaMailbox * mailbox)
{
    int i;
    GtkWidget *index;
    BalsaMailboxNode *mbnode;

    mbnode = balsa_find_mailbox(mailbox);
    if (!mbnode) {
        g_warning(_("Failed to find mailbox"));
        return;
    }

    index = balsa_window_find_current_index(balsa_app.main_window);

    /* If we currently have a page open, update the time last visited */
    if (index) {
      time(&BALSA_INDEX(index)->mailbox_node->last_use);
    }
    
    i = balsa_find_notebook_page_num(mailbox);
    if (i != -1) {
      gtk_notebook_set_current_page(GTK_NOTEBOOK(balsa_app.notebook), i);
        index = balsa_window_find_current_index(balsa_app.main_window);
      time(&BALSA_INDEX(index)->mailbox_node->last_use);
        balsa_index_set_column_widths(BALSA_INDEX(index));
    } else { /* page with mailbox not found, open it */
      balsa_window_open_mbnode(balsa_app.main_window, mbnode);

      balsa_window_set_filter_label(balsa_app.main_window,
                          libbalsa_mailbox_get_show(mailbox) == LB_MAILBOX_SHOW_TO);
      if (balsa_app.mblist->display_info)
          balsa_mblist_update_mailbox(balsa_app.mblist_tree_store,
                                        mailbox);
    }
    g_object_unref(mbnode);
    
    balsa_mblist_set_status_bar(mailbox);
}

void
balsa_mblist_close_mailbox(LibBalsaMailbox * mailbox)
{
    BalsaMailboxNode *mbnode;
    
    mbnode = balsa_find_mailbox(mailbox);
    if (!mbnode)  {
        g_warning(_("Failed to find mailbox"));
        return;
    }

    balsa_window_close_mbnode(balsa_app.main_window, mbnode);
    g_object_unref(mbnode);
}

/* balsa_mblist_close_lru_peer_mbx closes least recently used mailbox
 * on the same server as the one given as the argument: some IMAP
 * servers limit the number of simultaneously open connections. */
struct lru_data {
    GtkTreePath     *ancestor_path;
    BalsaMailboxNode *mbnode;
};

static gboolean
get_lru_descendant(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
                   gpointer data)
{
    struct lru_data *dt  = (struct lru_data*)data;
    BalsaMailboxNode *mbnode;

    if(!gtk_tree_path_is_descendant(path, dt->ancestor_path))
        return FALSE;
    gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
    if(mbnode->mailbox && libbalsa_mailbox_is_open(mbnode->mailbox) &&
       (!dt->mbnode || (mbnode->last_use < dt->mbnode->last_use)) )
        dt->mbnode = mbnode; 

    else g_object_unref(mbnode);
    return FALSE;
}

gboolean
balsa_mblist_close_lru_peer_mbx(BalsaMBList * mblist,
                                LibBalsaMailbox *mailbox)
{
    GtkTreeModel *model;
    GtkTreeIter   iter;
    struct lru_data dt;
    g_return_val_if_fail(mailbox, FALSE);

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(mblist));

    if(!balsa_find_iter_by_data(&iter, mailbox))
        return FALSE;
    dt.ancestor_path = gtk_tree_model_get_path(model, &iter);
    while(gtk_tree_path_get_depth(dt.ancestor_path)>1)
        gtk_tree_path_up(dt.ancestor_path);

    dt.mbnode = NULL;
    gtk_tree_model_foreach(model, get_lru_descendant, &dt);
    if(dt.mbnode) {
        balsa_window_close_mbnode(balsa_app.main_window, dt.mbnode);
        g_object_unref(dt.mbnode);
    }
    return dt.mbnode != NULL;
}

/* balsa_mblist_default_signal_bindings:
   connect signals useful for the left-hand side mailbox tree
   but useless for the transfer menu.
*/
void
balsa_mblist_default_signal_bindings(BalsaMBList * mblist)
{
    GtkTreeSelection *selection;

    g_signal_connect(G_OBJECT(mblist), "button_press_event",
                     G_CALLBACK(bmbl_button_press_cb), NULL);
    g_signal_connect_after(G_OBJECT(mblist), "size-allocate",
                           G_CALLBACK(bmbl_column_resize), NULL);
    gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(mblist),
                                         bmbl_drop_types,
                                         ELEMENTS(bmbl_drop_types),
                                         GDK_ACTION_DEFAULT |
                                         GDK_ACTION_COPY |
                                         GDK_ACTION_MOVE);
    g_signal_connect(G_OBJECT(mblist), "drag-data-received",
                     G_CALLBACK(bmbl_drag_cb), NULL);

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mblist));
    g_signal_connect(G_OBJECT(selection), "changed",
                     G_CALLBACK(bmbl_select_mailbox), NULL);
    g_signal_connect(G_OBJECT(mblist), "row-activated",
                     G_CALLBACK(bmbl_row_activated_cb), NULL);
}

/* bmbl_disconnect_mailbox_signals
 *
 * Remove the signals we attached to the mailboxes.
 */
static void
bmbl_real_disconnect_mbnode_signals(BalsaMailboxNode * mbnode,
                            GtkTreeModel * model)
{
    if (mbnode->mailbox)
        g_signal_handlers_disconnect_by_func(mbnode->mailbox,
                                             G_CALLBACK
                                             (bmbl_mailbox_changed_cb),
                                             NULL);
}

/* bmbl_store_redraw_mbnode
 * 
 * adds BalsaMailboxNodes to the mailbox list, choosing proper icon for them.
 * returns FALSE on failure (wrong parameters passed).
 * */
static gboolean
bmbl_store_redraw_mbnode(GtkTreeIter * iter, BalsaMailboxNode * mbnode)
{
    const gchar *in;
    gchar *name;
    gboolean expose = FALSE;

    g_return_val_if_fail(mbnode, FALSE);

    if (mbnode->mailbox) {
        LibBalsaMailbox *mailbox = mbnode->mailbox;
      static guint mailbox_changed_signal = 0;

      if (LIBBALSA_IS_MAILBOX_POP3(mailbox)) {
          g_assert_not_reached();
            in = NULL;
            name = NULL;
        } else {
          if(mailbox == balsa_app.draftbox)
            in = BALSA_PIXMAP_MBOX_DRAFT;
          else if(mailbox == balsa_app.inbox)
            in = BALSA_PIXMAP_MBOX_IN;
          else if(mailbox == balsa_app.outbox)
            in = BALSA_PIXMAP_MBOX_OUT;
          else if(mailbox == balsa_app.sentbox)
            in = BALSA_PIXMAP_MBOX_SENT;
          else if(mailbox == balsa_app.trash)
            in = GTK_STOCK_DELETE;
          else
            in = (libbalsa_mailbox_total_messages(mailbox) > 0)
            ? BALSA_PIXMAP_MBOX_TRAY_FULL
                : BALSA_PIXMAP_MBOX_TRAY_EMPTY;

            name = g_strdup(mailbox->name);

            /* Make sure the show column is set before showing the
             * mailbox in the list. */
          if (libbalsa_mailbox_get_show(mailbox) == LB_MAILBOX_SHOW_UNSET)
            libbalsa_mailbox_set_show(mailbox,
                                (mailbox == balsa_app.sentbox
                                 || mailbox == balsa_app.draftbox
                                 || mailbox == balsa_app.outbox)
                                ? LB_MAILBOX_SHOW_TO
                                : LB_MAILBOX_SHOW_FROM);
      }

      if (!mailbox_changed_signal)
          mailbox_changed_signal =
            g_signal_lookup("changed", LIBBALSA_TYPE_MAILBOX);
      if (!g_signal_has_handler_pending(G_OBJECT(mbnode->mailbox),
                                          mailbox_changed_signal, 0, TRUE)) {
          /* Now we have a mailbox: */
          g_signal_connect(mbnode->mailbox, "changed",
                       G_CALLBACK(bmbl_mailbox_changed_cb),
                       NULL);
          /* If necessary, expand rows to expose this mailbox after
           * setting its mbnode in the tree-store. */
          expose = libbalsa_mailbox_get_exposed(mbnode->mailbox);
      }
    } else {
      /* new directory, but not a mailbox */
      in = BALSA_PIXMAP_MBOX_DIR_CLOSED;
        name = g_path_get_basename(mbnode->name);
    }

    gtk_tree_store_set(balsa_app.mblist_tree_store, iter,
                       MBNODE_COLUMN, mbnode,
                       ICON_COLUMN,   
                       gtk_widget_render_icon
                           (GTK_WIDGET(balsa_app.main_window), in,
                            GTK_ICON_SIZE_MENU, NULL),
                       NAME_COLUMN,   name,
                       WEIGHT_COLUMN, PANGO_WEIGHT_NORMAL,
                       STYLE_COLUMN, PANGO_STYLE_NORMAL,
                       UNREAD_COLUMN, "",
                       TOTAL_COLUMN,  "",
                       -1);
    g_free(name);

    if (mbnode->mailbox) {
      GtkTreeModel *model = GTK_TREE_MODEL(balsa_app.mblist_tree_store);
      if (expose) {
          GtkTreePath *path = gtk_tree_model_get_path(model, iter);
          bmbl_expand_to_row(balsa_app.mblist, path);
          gtk_tree_path_free(path);
      }
      bmbl_node_style(model, iter, -1);
    }

    return TRUE;
}

/* balsa_mblist_update_mailbox [MBG]
 * 
 * mblist: the mailbox list that contains the mailbox
 * mbnode:  the mailbox node that you wish to update
 * 
 * Description: the function looks for the mailbox in the mblist, if
 * it's there it changes the style (and fills the info columns)
 * depending on the mailbox variables unread_messages and
 * total_messages. Selects the mailbox (important when previous mailbox
 * was closed).
 * */
static void
bmbl_update_mailbox(GtkTreeStore * store, LibBalsaMailbox * mailbox,
                gint total_messages)
{
    GtkTreeModel *model = GTK_TREE_MODEL(store);
    GtkTreeIter iter;
    GtkWidget *bindex;

    /* try and find the mailbox */
    if (!balsa_find_iter_by_data(&iter, mailbox))
        return;

    bmbl_node_style(model, &iter, total_messages);

    bindex = balsa_window_find_current_index(balsa_app.main_window);
    if (!bindex || mailbox != BALSA_INDEX(bindex)->mailbox_node->mailbox)
        return;

    balsa_mblist_set_status_bar(mailbox);
}

void
balsa_mblist_update_mailbox(GtkTreeStore * store,
                      LibBalsaMailbox * mailbox)
{
    bmbl_update_mailbox(store, mailbox, -1);
}

/* bmbl_node_style [MBG]
 * 
 * model:  The model containing the mailbox
 * iter : the iterator pointing on the mailbox node
 * Description: A function to actually do the changing of the style,
 * and is called by both balsa_mblist_update_mailbox, and
 * balsa_mblist_check_new
 *
 * NOTES: ignore special mailboxes.
 * */
static void
bmbl_node_style(GtkTreeModel * model, GtkTreeIter * iter, gint total_messages)
{
    BalsaMailboxNode * mbnode;
    LibBalsaMailbox *mailbox;
    const gchar *icon;
    gchar *text;
    GtkTreeIter parent;
    gboolean has_unread_child;

    gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
    mailbox = mbnode->mailbox;

    /* SHOW UNREAD for special mailboxes? */
    if (!(mailbox == balsa_app.sentbox || mailbox == balsa_app.outbox ||
          mailbox == balsa_app.draftbox || mailbox == balsa_app.trash)) {
        if (mailbox->has_unread_messages) {

            /* set the style of the unread maibox list, even if it's already 
             * set... in case the user has changed the colour or font since the
             * last style update */
            icon = BALSA_PIXMAP_MBOX_TRAY_FULL;
            gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                               ICON_COLUMN,
                               gtk_widget_render_icon
                                   (GTK_WIDGET(balsa_app.main_window),
                                    BALSA_PIXMAP_MBOX_TRAY_FULL,
                                    GTK_ICON_SIZE_MENU, NULL),
                               WEIGHT_COLUMN, PANGO_WEIGHT_BOLD, -1);

            mbnode->style |= MBNODE_STYLE_NEW_MAIL;

            /* If we have a count of the unread messages, and we are showing
             * columns, put the number in the unread column */
            if (mailbox->unread_messages > 0) {
                text = g_strdup_printf("%ld", mailbox->unread_messages);
                gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                                   UNREAD_COLUMN, text, -1);
                g_free(text);

                mbnode->style |= MBNODE_STYLE_UNREAD_MESSAGES;
            } else
                gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                                   UNREAD_COLUMN, "", -1);
        } else {
            /* If the clist entry currently has the unread messages icon, set
             * it back, otherwise we can ignore this. */
            if (mbnode->style & MBNODE_STYLE_NEW_MAIL) {
                if (mailbox == balsa_app.inbox)
                    icon = BALSA_PIXMAP_MBOX_IN;
                else
                    icon = BALSA_PIXMAP_MBOX_TRAY_EMPTY;

                gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                                   ICON_COLUMN,
                                   gtk_widget_render_icon
                                       (GTK_WIDGET(balsa_app.main_window),
                                        icon, GTK_ICON_SIZE_MENU, NULL),
                                   WEIGHT_COLUMN, PANGO_WEIGHT_NORMAL,
                                   STYLE_COLUMN, PANGO_STYLE_NORMAL, -1);

                mbnode->style &= ~MBNODE_STYLE_NEW_MAIL;
            }

            /* If we're showing unread column info, get rid of whatever's
             * there Also set the flag */
            gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                               UNREAD_COLUMN, "0", -1);
            mbnode->style &= ~MBNODE_STYLE_UNREAD_MESSAGES;
        }
    }
    /* We only want to do this if the mailbox is open, otherwise leave
     * the message numbers untouched in the display */
    if (total_messages >= 0 || MAILBOX_OPEN(mailbox)) {
      if (total_messages < 0)
          total_messages = libbalsa_mailbox_total_messages(mailbox);
        if (total_messages > 0) {
            text =
            g_strdup_printf("%d", total_messages);
            gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                               TOTAL_COLUMN, text, -1);
            g_free(text);

            mbnode->style |= MBNODE_STYLE_TOTAL_MESSAGES;
        } else {
            gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                               TOTAL_COLUMN, "0", -1);
            mbnode->style &= ~MBNODE_STYLE_TOTAL_MESSAGES;
        }
    }
    g_object_unref(mbnode);

    /* Do the folder styles as well */
    has_unread_child = mailbox->has_unread_messages;
    while (gtk_tree_model_iter_parent(model, &parent, iter)) {
      *iter = parent;
      gtk_tree_model_get(model, &parent, 0, &mbnode, -1);
      if (!has_unread_child) {
          /* Check all the children of this parent. */
          GtkTreeIter child;
          /* We know it has at least one child. */
          gtk_tree_model_iter_children(model, &child, &parent);
          do {
            BalsaMailboxNode *mn;
            gtk_tree_model_get(model, &child, 0, &mn, -1);
            if (mn->style & (MBNODE_STYLE_NEW_MAIL |
                         MBNODE_STYLE_UNREAD_CHILD))
                has_unread_child = TRUE;
            g_object_unref(mn);
          } while (!has_unread_child && gtk_tree_model_iter_next(model, &child));
      }
      if (has_unread_child) {
          mbnode->style |= MBNODE_STYLE_UNREAD_CHILD;
          gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
                         STYLE_COLUMN, PANGO_STYLE_OBLIQUE, -1);
      } else {
          mbnode->style &= ~MBNODE_STYLE_UNREAD_CHILD;
          gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
                         STYLE_COLUMN, PANGO_STYLE_NORMAL, -1);
      }
      g_object_unref(mbnode);
    }
}

/* bmbl_core_mailbox
 * 
 * Simple function, if the mailbox is one of the five "core" mailboxes
 * (i.e. Inbox, Sentbox...) it returns an integer representing it's
 * place in the desired heirarchy in the mblist.  If the mailbox is
 * not a core mailbox it returns zero. 
 * */
static gint
bmbl_core_mailbox(LibBalsaMailbox* mailbox)
{
    static const int num_core_mailboxes = 5;
    LibBalsaMailbox* core_mailbox[] = {
#if !defined(ENABLE_TOUCH_UI)
        balsa_app.inbox,
        balsa_app.sentbox,
        balsa_app.draftbox,
        balsa_app.outbox,
        balsa_app.trash
#else /* defined(ENABLE_TOUCH_UI) */
        balsa_app.inbox,
        balsa_app.draftbox,
        balsa_app.outbox,
        balsa_app.sentbox,
        balsa_app.trash
#endif /* defined(ENABLE_TOUCH_UI) */
    };
    gint i = 0;
    
    for (i = 0; i < num_core_mailboxes; ++i) {
        if (mailbox == core_mailbox[i]) {
            /* we want to return as if from a base-1 array */
            return num_core_mailboxes - i + 1;
        }
    }
    
    /* if we couldn't find the mailbox, return 0 */
    return 0;
}

gboolean
balsa_mblist_focus_mailbox(BalsaMBList * mblist, LibBalsaMailbox * mailbox)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkTreeSelection *selection;

    g_return_val_if_fail(mblist, FALSE);

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(mblist));
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mblist));

    if (mailbox && balsa_find_iter_by_data(&iter, mailbox)) {
        if (!gtk_tree_selection_iter_is_selected(selection, &iter)) {
            /* moving selection to the respective mailbox.
               this is neccessary when the previous mailbox was closed and
               redundant if the mailboxes were switched (notebook_switch_page)
               or the mailbox is checked for the new mail arrival
             */
            GtkTreePath *path;

            path = gtk_tree_model_get_path(model, &iter);
            bmbl_expand_to_row(mblist, path);
            gtk_tree_selection_select_path(selection, path);
            gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(mblist), path, NULL,
                                         FALSE, 0, 0);
            gtk_tree_path_free(path);
        }
        return TRUE;
    } else {
        gtk_tree_selection_unselect_all(selection);
        return FALSE;
    }
}

/* mblist_remove_mailbox_node:
   remove give mailbox node from the mailbox tree.
   Return TRUE (or equivalent) on success, FALSE on failure.
*/

gboolean
balsa_mblist_mailbox_node_remove(BalsaMailboxNode* mbnode)
{
    GtkTreeIter iter;

    g_return_val_if_fail(mbnode, FALSE);

    if (balsa_find_iter_by_data(&iter, mbnode)) {
      bmbl_real_disconnect_mbnode_signals(mbnode,
                                  GTK_TREE_MODEL
                                  (balsa_app.mblist_tree_store));
        gtk_tree_store_remove(balsa_app.mblist_tree_store, &iter);

        return TRUE;
    }

    return FALSE;
}

/* balsa_mblist_mru_menu:
 * a menu showing a list of recently used mailboxes, with an option for
 * selecting one from the whole mailbox tree
 */

/* The info that needs to be passed around */
struct _BalsaMBListMRUEntry {
    GtkWindow *window;
    GList **url_list;
    GCallback user_func;
    gpointer user_data;
    gchar *url;
    GCallback setup_cb;
};
typedef struct _BalsaMBListMRUEntry BalsaMBListMRUEntry;

/* The callback that's passed in must fit this prototype, although it's
 * cast as a GCallback */
typedef void (*MRUCallback) (gchar * url, gpointer user_data);
/* Callback used internally for letting the option menu know that the
 * option menu needs to be set up */
typedef void (*MRUSetup) (gpointer user_data);

/* Forward references */
static GtkWidget *bmbl_mru_menu(GtkWindow * window, GList ** url_list,
                                GCallback user_func, gpointer user_data,
                                gboolean allow_empty, GCallback setup_cb);
static BalsaMBListMRUEntry *bmbl_mru_new(GList ** url_list,
                                         GCallback user_func,
                                         gpointer user_data,
                                         gchar * url);
static void bmbl_mru_free(BalsaMBListMRUEntry * mru);
static void bmbl_mru_activate_cb(GtkWidget * widget, gpointer data);
static void bmbl_mru_show_tree(GtkWidget * widget, gpointer data);
static void bmbl_mru_selected_cb(GtkTreeSelection * selection,
                                 gpointer data);
static void bmbl_mru_activated_cb(GtkTreeView * tree_view,
                                  GtkTreePath * path,
                                  GtkTreeViewColumn * column,
                                  gpointer data);

/*
 * balsa_mblist_mru_menu:
 *
 * window:      parent window for the `Other...' dialog;
 * url_list:    pointer to a list of urls;
 * user_func:   called when an item is selected, with the url and
 *              the user_data as arguments;
 * user_data:   passed to the user_func callback.
 *
 * Returns a pointer to a GtkMenu.
 *
 * Takes a list of urls and creates a menu with an entry for each one
 * that resolves to a mailbox, labeled with the mailbox name, with a
 * last entry that pops up the whole mailbox tree. When an item is
 * clicked, user_func is called with the url and user_data as
 * arguments, and the url_list is updated.
 */
GtkWidget *
balsa_mblist_mru_menu(GtkWindow * window, GList ** url_list,
                      GCallback user_func, gpointer user_data)
{
    g_return_val_if_fail(url_list != NULL, NULL);
    return bmbl_mru_menu(window, url_list, user_func, user_data, FALSE,
                         NULL);
}

/*
 * bmbl_mru_menu:
 *
 * window, url_list, user_func, user_data:
 *              as for balsa_mblist_mru_menu;
 * allow_empty: if TRUE, a list with an empty url
 *              will be allowed into the menu;
 * setup_cb:    called when the tree has been displayed, to allow the
 *              display to be reset.
 *
 * Returns the GtkMenu.
 */
static GtkWidget *
bmbl_mru_menu(GtkWindow * window, GList ** url_list,
              GCallback user_func, gpointer user_data,
              gboolean allow_empty, GCallback setup_cb)
{
    GtkWidget *menu = gtk_menu_new();
    GtkWidget *item;
    GList *list;
    BalsaMBListMRUEntry *mru;

    for (list = *url_list; list; list = g_list_next(list)) {
        gchar *url = list->data;
        LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);

        if (mailbox || (allow_empty && !*url)) {
            mru = bmbl_mru_new(url_list, user_func, user_data, url);
            item =
                gtk_menu_item_new_with_label(mailbox ? mailbox->name : "");
            gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
            g_signal_connect_data(item, "activate",
                                  G_CALLBACK(bmbl_mru_activate_cb), mru,
                                  (GClosureNotify) bmbl_mru_free,
                                  (GConnectFlags) 0);
        }
    }

    gtk_menu_shell_append(GTK_MENU_SHELL(menu),
                          gtk_separator_menu_item_new());

    mru = bmbl_mru_new(url_list, user_func, user_data, NULL);
    mru->window = window;
    mru->setup_cb = setup_cb;
    item = gtk_menu_item_new_with_mnemonic(_("_Other..."));
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    g_signal_connect_data(item, "activate",
                          G_CALLBACK(bmbl_mru_show_tree), mru,
                          (GClosureNotify) g_free, (GConnectFlags) 0);

    gtk_widget_show_all(menu);

    return menu;
}

/*
 * bmbl_mru_new:
 *
 * url_list, user_func, user_data:
 *              as for balsa_mblist_mru_menu;
 * url:         url of a mailbox.
 *
 * Returns a newly allocated BalsaMBListMRUEntry structure, initialized
 * with the data.
 */
static BalsaMBListMRUEntry *
bmbl_mru_new(GList ** url_list, GCallback user_func, gpointer user_data,
             gchar * url)
{
    BalsaMBListMRUEntry *mru = g_new(BalsaMBListMRUEntry, 1);

    mru->url_list = url_list;
    mru->user_func = user_func;
    mru->user_data = user_data;
    mru->url = g_strdup(url);

    return mru;
}

static void
bmbl_mru_free(BalsaMBListMRUEntry * mru)
{
    g_free(mru->url);
    g_free(mru);
}

/*
 * bmbl_mru_activate_cb:
 *
 * Callback for the "activate" signal of a menu item.
 */
static void
bmbl_mru_activate_cb(GtkWidget * item, gpointer data)
{
    BalsaMBListMRUEntry *mru = (BalsaMBListMRUEntry *) data;

    balsa_mblist_mru_add(mru->url_list, mru->url);
    if (mru->user_func)
        ((MRUCallback) mru->user_func) (mru->url, mru->user_data);
}

/*
 * bmbl_mru_show_tree:
 *
 * Callback for the "activate" signal of the last menu item.
 * Pops up a GtkDialog with a BalsaMBList.
 */
static void
bmbl_mru_show_tree(GtkWidget * widget, gpointer data)
{
    BalsaMBListMRUEntry *mru = data;
    GtkWidget *dialog =
        gtk_dialog_new_with_buttons(_("Choose destination folder"),
                                    mru->window,
                                    GTK_DIALOG_MODAL,
                                    GTK_STOCK_CANCEL,
                                    GTK_RESPONSE_CANCEL,
                                    NULL);
    GtkWidget *scroll;
    GtkWidget *mblist = balsa_mblist_new();
    GtkTreeSelection *selection =
        gtk_tree_view_get_selection(GTK_TREE_VIEW(mblist));
    GtkRequisition req;

    scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
                                   GTK_POLICY_AUTOMATIC,
                                   GTK_POLICY_AUTOMATIC);

    g_signal_connect(selection, "changed",
                     G_CALLBACK(bmbl_mru_selected_cb), data);
    g_signal_connect(mblist, "row-activated",
                     G_CALLBACK(bmbl_mru_activated_cb), data);

    /* Force the mailbox list to be a reasonable size. */
    gtk_widget_size_request(mblist, &req);
    if (req.height > balsa_app.mw_height)
        req.height = balsa_app.mw_height;
    /* For the mailbox list width, we use the one used on the main
     * window enhanced somewhat. This is the user choice and required
     * because the mblist widget saves the size in
     * balsa_app.mblist_width */
    req.width = balsa_app.mblist_width> 200 ? balsa_app.mblist_width : 200;
    gtk_widget_set_size_request(GTK_WIDGET(mblist), req.width, req.height);

    gtk_container_add(GTK_CONTAINER(scroll), mblist);
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), scroll);
    gtk_widget_show_all(scroll);

    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
    if (mru->setup_cb)
        ((MRUSetup) mru->setup_cb) (mru->user_data);
}

/*
 * bmbl_mru_selected_cb:
 *
 * Callback for the "changed" signal of the GtkTreeSelection in the
 * BalsaMBList object. This permits one-click selection of a mailbox.
 *
 * Emulates selecting one of the other menu items, and closes the dialog.
 */
static void
bmbl_mru_selected_cb(GtkTreeSelection * selection, gpointer data)
{
    GdkEvent *event;
    GtkTreeView *tree_view;
    GtkTreePath *path;

    if (!data)
        return;

    event = gtk_get_current_event();
    if (!event)
        return;

    tree_view = gtk_tree_selection_get_tree_view(selection);
    if (event->type != GDK_BUTTON_PRESS ||
        !gtk_tree_view_get_path_at_pos(tree_view, event->button.x,
                                       event->button.y, &path,
                                       NULL, NULL, NULL)) {
        gtk_tree_selection_unselect_all(selection);
        gdk_event_free(event);
        return;
    }

    if (gtk_tree_selection_path_is_selected(selection, path)) {
        GtkTreeModel *model;
        GtkTreeIter iter;
        BalsaMailboxNode *mbnode;

        model = gtk_tree_view_get_model(tree_view);
        gtk_tree_model_get_iter(model, &iter, path);
        gtk_tree_model_get(model, &iter, 0, &mbnode, -1);
        ((BalsaMBListMRUEntry *) data)->url = g_strdup(mbnode->mailbox->url);
      g_object_unref(mbnode);
        bmbl_mru_activate_cb(NULL, data);

        gtk_dialog_response(GTK_DIALOG
                            (gtk_widget_get_ancestor
                             (GTK_WIDGET(tree_view), GTK_TYPE_DIALOG)),
                            GTK_RESPONSE_OK);
    }

    gtk_tree_path_free(path);
    gdk_event_free(event);
}

/*
 * bmbl_mru_activated_cb:
 *
 * Callback for the "row-activated" signal of the GtkTreeView in the
 * BalsaMBList object. This permits keyboard selection of a mailbox.
 */
static void
bmbl_mru_activated_cb(GtkTreeView * tree_view, GtkTreePath * path,
                      GtkTreeViewColumn * column, gpointer data)
{
    GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
    GtkTreeIter iter;
    BalsaMailboxNode *mbnode;

    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
    g_return_if_fail(mbnode != NULL);

    if (mbnode->mailbox) {
        ((BalsaMBListMRUEntry *) data)->url =
            g_strdup(mbnode->mailbox->url);
        bmbl_mru_activate_cb(NULL, data);

        gtk_dialog_response(GTK_DIALOG
                            (gtk_widget_get_ancestor
                             (GTK_WIDGET(tree_view), GTK_TYPE_DIALOG)),
                            GTK_RESPONSE_OK);
    }
    g_object_unref(mbnode);
}

/* balsa_mblist_mru_add/drop:
   Add given folder's url to mailbox-recently-used list.
   Drop it first, so that if it's already there, it moves to the top.
*/
#define FOLDER_MRU_LENGTH 10
void
balsa_mblist_mru_add(GList ** list, const gchar * url)
{
    balsa_mblist_mru_drop(list, url);
    while (g_list_length(*list) >= FOLDER_MRU_LENGTH) {
        GList *tmp = g_list_last(*list);

        g_free(tmp->data);
        *list = g_list_delete_link(*list, tmp);
    }
    *list = g_list_prepend(*list, g_strdup(url));
}

void
balsa_mblist_mru_drop(GList ** list, const gchar * url)
{
    GList *tmp;

    for (tmp = *list; tmp; tmp = g_list_next(tmp)) {
        if (!strcmp((char *) tmp->data, url)) {
            g_free(tmp->data);
            *list = g_list_delete_link(*list, tmp);
            break;
        }
    }
}

/* balsa_mblist_mru_option_menu: create a GtkComboBox to manage an MRU
 * list */

/* The info that needs to be passed around */
struct _BalsaMBListMRUOption {
    GtkWindow *window;
    GList **url_list;
    GSList *real_urls;
};
typedef struct _BalsaMBListMRUOption BalsaMBListMRUOption;

/* Helper. */
static void bmbl_mru_combo_box_changed(GtkComboBox * combo_box,
                                       BalsaMBListMRUOption * mro);
static void
bmbl_mru_combo_box_setup(GtkComboBox * combo_box)
{
    BalsaMBListMRUOption *mro =
        g_object_get_data(G_OBJECT(combo_box), "mro");
    GList *list;
#if GTK_CHECK_VERSION(2, 6, 0)
    GtkListStore *store;
    GtkTreeIter iter;
#else                           /* GTK_CHECK_VERSION(2, 6, 0) */
    gint i;
#endif                          /* GTK_CHECK_VERSION(2, 6, 0) */

    gtk_combo_box_set_active(combo_box, -1);
#if GTK_CHECK_VERSION(2, 6, 0)
    store = GTK_LIST_STORE(gtk_combo_box_get_model(combo_box));
    gtk_list_store_clear(store);
#else                           /* GTK_CHECK_VERSION(2, 6, 0) */
    for (i = g_slist_length(mro->real_urls) + 1; --i >= 0;)
        gtk_combo_box_remove_text(combo_box, i);
#endif                          /* GTK_CHECK_VERSION(2, 6, 0) */
    g_slist_foreach(mro->real_urls, (GFunc) g_free, NULL);
    g_slist_free(mro->real_urls);
    mro->real_urls = NULL;

    for (list = *mro->url_list; list; list = list->next) {
        const gchar *url = list->data;
        LibBalsaMailbox *mailbox;

        if ((mailbox = balsa_find_mailbox_by_url(url)) || !*url) {
#if GTK_CHECK_VERSION(2, 6, 0)
          gtk_list_store_append(store, &iter);
          gtk_list_store_set(store, &iter,
                               0, mailbox ? mailbox->name : "",
                               1, FALSE, -1);
#else                           /* GTK_CHECK_VERSION(2, 6, 0) */
            gtk_combo_box_append_text(GTK_COMBO_BOX(combo_box),
                                      mailbox ? mailbox->name : "");
#endif                          /* GTK_CHECK_VERSION(2, 6, 0) */
            mro->real_urls = g_slist_append(mro->real_urls, g_strdup(url));
        }
    }

#if GTK_CHECK_VERSION(2, 6, 0)
    /* Separator: */
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, 1, TRUE, -1);
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter,
                       0, _("Other..."),
                   1, FALSE, -1);
#else                           /* GTK_CHECK_VERSION(2, 6, 0) */
    gtk_combo_box_append_text(GTK_COMBO_BOX(combo_box), _("Other..."));
#endif                          /* GTK_CHECK_VERSION(2, 6, 0) */
    gtk_combo_box_set_active(combo_box, 0);
}

/* Callbacks */
static void
bmbl_mru_combo_box_changed(GtkComboBox * combo_box,
                           BalsaMBListMRUOption * mro)
{
    BalsaMBListMRUEntry mru;
    const gchar *url;
    gint active = gtk_combo_box_get_active(combo_box);

    if (active < 0)
      return;
    if ((url = g_slist_nth_data(mro->real_urls, active))) {
      /* Move this url to the top. */
      balsa_mblist_mru_add(mro->url_list, url);
        return;
    }

    /* User clicked on "Other..." */
    mru.window = mro->window;
    mru.url_list = mro->url_list;
    mru.user_func = NULL;
    mru.setup_cb = NULL;
    mru.user_data = combo_box;
    mru.url = NULL;
    bmbl_mru_show_tree(NULL, &mru);
    g_free(mru.url);
    bmbl_mru_combo_box_setup(combo_box);
}

static void
bmbl_mru_combo_box_destroy_cb(BalsaMBListMRUOption * mro)
{
    g_slist_foreach(mro->real_urls, (GFunc) g_free, NULL);
    g_slist_free(mro->real_urls);
    g_free(mro);
}

/*
 * balsa_mblist_mru_option_menu:
 *
 * window:      parent window for the dialog;
 * url_list:    pointer to a list of urls;
 *
 * Returns a GtkComboBox.
 *
 * Takes a list of urls and creates a combo-box with an entry for
 * each one that resolves to a mailbox, labeled with the mailbox name,
 * including the special case of an empty url and a NULL mailbox.
 * Adds a last entry that pops up the whole mailbox tree. When an item
 * is clicked, the url_list is updated.
 */
#if GTK_CHECK_VERSION(2, 6, 0)
static gboolean
bmbl_mru_separator_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
    gboolean is_sep;

    gtk_tree_model_get(model, iter, 1, &is_sep, -1);

    return is_sep;
}
#endif                          /* GTK_CHECK_VERSION(2, 6, 0) */

GtkWidget *
balsa_mblist_mru_option_menu(GtkWindow * window, GList ** url_list)
{
    GtkWidget *combo_box;
    BalsaMBListMRUOption *mro;
#if GTK_CHECK_VERSION(2, 6, 0)
    GtkListStore *store;
    GtkCellRenderer *renderer;
#endif                          /* GTK_CHECK_VERSION(2, 6, 0) */

    g_return_val_if_fail(url_list != NULL, NULL);

#if GTK_CHECK_VERSION(2, 6, 0)
    store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_BOOLEAN);
    combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
    renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), renderer,
                                   "text", 0, NULL);
    gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(combo_box),
                                         bmbl_mru_separator_func, NULL,
                                         NULL);
#else                           /* GTK_CHECK_VERSION(2, 6, 0) */
    combo_box = gtk_combo_box_new_text();
#endif                          /* GTK_CHECK_VERSION(2, 6, 0) */
    mro = g_new(BalsaMBListMRUOption, 1);

    mro->window = window;
    mro->url_list = url_list;
    mro->real_urls = NULL;

    g_object_set_data_full(G_OBJECT(combo_box), "mro", mro,
                           (GDestroyNotify) bmbl_mru_combo_box_destroy_cb);
    bmbl_mru_combo_box_setup(GTK_COMBO_BOX(combo_box));
    g_signal_connect(G_OBJECT(combo_box), "changed",
                     G_CALLBACK(bmbl_mru_combo_box_changed), mro);

    return combo_box;
}

/*
 * balsa_mblist_mru_option_menu_set
 *
 * combo_box: a GtkComboBox created by balsa_mblist_mru_option_menu;
 * url:       URL of a mailbox
 *
 * Adds url to the front of the url_list managed by combo_box, resets
 * combo_box to show the new url, and stores a copy in the mro
 * structure.
 */
void
balsa_mblist_mru_option_menu_set(GtkWidget * combo_box, const gchar * url)
{
    BalsaMBListMRUOption *mro =
        g_object_get_data(G_OBJECT(combo_box), "mro");

    balsa_mblist_mru_add(mro->url_list, url);
    bmbl_mru_combo_box_setup(GTK_COMBO_BOX(combo_box));
}

/*
 * balsa_mblist_mru_option_menu_get
 *
 * combo_box: a GtkComboBox created by balsa_mblist_mru_option_menu.
 *
 * Returns the address of the current URL.
 *
 * Note that the url is held in the mro structure, and is freed when the
 * widget is destroyed. The calling code must make its own copy of the
 * string if it is needed more than temporarily.
 */
const gchar *
balsa_mblist_mru_option_menu_get(GtkWidget * combo_box)
{
    gint active;
    BalsaMBListMRUOption *mro =
        g_object_get_data(G_OBJECT(combo_box), "mro");

    active = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_box));

    return g_slist_nth_data(mro->real_urls, active);
}

void
balsa_mblist_set_status_bar(LibBalsaMailbox * mailbox)
{
    guint total_messages = libbalsa_mailbox_total_messages(mailbox);
    gchar *desc;
    gchar *desc1, *desc2;

    desc1 = g_strdup_printf(ngettext("Shown mailbox: %s with %d message, ",
                             "Shown mailbox: %s with %d messages, ",
                             total_messages),
                      mailbox->name, total_messages);
    /* xgettext: this is the second part of the message
     * "Shown mailbox: %s with %d messages, %ld new". */
    desc2 = g_strdup_printf(ngettext("%ld new", "%ld new",
                             mailbox->unread_messages),
                      mailbox->unread_messages);
    desc = g_strconcat(desc1, desc2, NULL);

    gnome_appbar_set_default(balsa_app.appbar, desc);

    g_free(desc);
    g_free(desc1);
    g_free(desc2);
}

static void
bmbl_expand_to_row(BalsaMBList * mblist, GtkTreePath * path)
{
    GtkTreePath *tmp = gtk_tree_path_copy(path);

    if (gtk_tree_path_up(tmp) && gtk_tree_path_get_depth(tmp) > 0
        && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(mblist), tmp)) {
      gtk_tree_view_expand_to_path(GTK_TREE_VIEW(mblist), tmp);
    }

    gtk_tree_path_free(tmp);
}

/* Make a new row for mbnode in balsa_app.mblist_tree_store; the row
 * will be a child to the row for root, if we find it, and a top-level
 * row otherwise. */
void 
balsa_mblist_mailbox_node_append(BalsaMailboxNode * root,
                         BalsaMailboxNode * mbnode)
{
    gboolean is_sub_thread = libbalsa_am_i_subthread();
    GtkTreeModel *model;
    GtkTreeIter parent;
    GtkTreeIter *parent_iter = NULL;
    GtkTreeIter iter;

    if (is_sub_thread)
      gdk_threads_enter();

    model = GTK_TREE_MODEL(balsa_app.mblist_tree_store);
    if (root && balsa_find_iter_by_data(&parent, root))
      parent_iter = &parent;
    gtk_tree_store_append(balsa_app.mblist_tree_store, &iter, parent_iter);
    bmbl_store_redraw_mbnode(&iter, mbnode);

    /* The tree-store owns mbnode. */
    g_object_unref(mbnode);

    if (is_sub_thread)
      gdk_threads_leave();
}

/* Rerender a row after its properties have changed. */
void
balsa_mblist_mailbox_node_redraw(BalsaMailboxNode * mbnode)
{
    GtkTreeIter iter;
    if (balsa_find_iter_by_data(&iter, mbnode))
      bmbl_store_redraw_mbnode(&iter, mbnode);
}

Generated by  Doxygen 1.6.0   Back to index