1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  * Copyright (C) 1997-2013 Stuart Parmenter and others,
4  *                         See the file AUTHORS for a list.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21 
22 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
23 # include "config.h"
24 #endif                          /* HAVE_CONFIG_H */
25 #include "balsa-mblist.h"
26 
27 /* #include <gtk/gtkfeatures.h> */
28 #include <string.h>
29 
30 #include "balsa-app.h"
31 #include "balsa-icons.h"
32 #include "balsa-index.h"
33 #include "libbalsa.h"
34 #include <glib/gi18n.h>
35 #include "mailbox-node.h"
36 #include "main-window.h"
37 
38 #if HAVE_MACOSX_DESKTOP
39 #  include "macosx-helpers.h"
40 #endif
41 
42 /* Column numbers (used for sort_column_id): */
43 typedef enum {
44     BMBL_TREE_COLUMN_NAME = 1,
45     BMBL_TREE_COLUMN_UNREAD,
46     BMBL_TREE_COLUMN_TOTAL
47 } BmblTreeColumnId;
48 
49 /* object arguments */
50 enum {
51     PROP_0,
52     PROP_SHOW_CONTENT_INFO
53 };
54 
55 /* Drag and Drop stuff */
56 enum {
57     TARGET_MESSAGES
58 };
59 
60 /* tree model columns */
61 enum {
62     MBNODE_COLUMN = 0,         /* we use 0 in other code */
63     ICON_COLUMN,
64     NAME_COLUMN,
65     WEIGHT_COLUMN,
66     STYLE_COLUMN,
67     UNREAD_COLUMN,
68     TOTAL_COLUMN,
69     N_COLUMNS
70 };
71 
72 /* signals */
73 
74 enum {
75     HAS_UNREAD_MAILBOX,
76     LAST_SIGNAL
77 };
78 static gint balsa_mblist_signals[LAST_SIGNAL] = { 0 };
79 
80 static GtkTargetEntry bmbl_drop_types[] = {
81     {"x-application/x-message-list", GTK_TARGET_SAME_APP, TARGET_MESSAGES}
82 };
83 
84 static GtkTreeViewClass *parent_class = NULL;
85 
86 /* class methods */
87 static void bmbl_class_init(BalsaMBListClass * klass);
88 static void bmbl_set_property(GObject * object, guint prop_id,
89                               const GValue * value, GParamSpec * pspec);
90 static void bmbl_get_property(GObject * object, guint prop_id,
91                               GValue * value, GParamSpec * pspec);
92 static gboolean bmbl_drag_motion(GtkWidget * mblist,
93                                  GdkDragContext * context, gint x, gint y,
94                                  guint time);
95 static gboolean bmbl_popup_menu(GtkWidget * widget);
96 static void bmbl_select_mailbox(GtkTreeSelection * selection,
97                                 gpointer data);
98 static void bmbl_init(BalsaMBList * mblist);
99 static gboolean bmbl_selection_func(GtkTreeSelection * selection,
100                                     GtkTreeModel * model,
101                                     GtkTreePath * path,
102                                     gboolean path_currently_selected,
103                                     gpointer data);
104 
105 /* callbacks */
106 static void bmbl_tree_expand(GtkTreeView * tree_view, GtkTreeIter * iter,
107                              GtkTreePath * path, gpointer data);
108 static void bmbl_tree_collapse(GtkTreeView * tree_view, GtkTreeIter * iter,
109                                GtkTreePath * path, gpointer data);
110 static gint bmbl_row_compare(GtkTreeModel * model,
111                              GtkTreeIter * iter1,
112                              GtkTreeIter * iter2, gpointer data);
113 static gboolean bmbl_button_press_cb(GtkWidget * widget,
114                                      GdkEventButton * event,
115                                      gpointer data);
116 static void bmbl_column_resize(GtkWidget * widget,
117                                GtkAllocation * allocation, gpointer data);
118 static void bmbl_drag_cb(GtkWidget * widget, GdkDragContext * context,
119                          gint x, gint y,
120                          GtkSelectionData * selection_data, guint info,
121                          guint32 time, gpointer data);
122 static void bmbl_row_activated_cb(GtkTreeView * tree_view,
123                                   GtkTreePath * path,
124                                   GtkTreeViewColumn * column,
125                                   gpointer data);
126 static void bmbl_mailbox_changed_cb(LibBalsaMailbox * mailbox,
127 				    gpointer data);
128 /* helpers */
129 static gboolean bmbl_find_all_unread_mboxes_func(GtkTreeModel * model,
130                                                  GtkTreePath * path,
131                                                  GtkTreeIter * iter,
132                                                  gpointer data);
133 static void bmbl_real_disconnect_mbnode_signals(BalsaMailboxNode * mbnode,
134 					      GtkTreeModel * model);
135 static gboolean bmbl_store_redraw_mbnode(GtkTreeIter * iter,
136 					 BalsaMailboxNode * mbnode);
137 static void bmbl_node_style(GtkTreeModel * model, GtkTreeIter * iter);
138 static gint bmbl_core_mailbox(LibBalsaMailbox * mailbox);
139 static void bmbl_do_popup(GtkTreeView * tree_view, GtkTreePath * path,
140                           GdkEventButton * event);
141 static void bmbl_expand_to_row(BalsaMBList * mblist, GtkTreePath * path);
142 /* end of prototypes */
143 
144 /* class methods */
145 
146 GType
balsa_mblist_get_type(void)147 balsa_mblist_get_type(void)
148 {
149     static GType mblist_type = 0;
150 
151     if (!mblist_type) {
152 	static const GTypeInfo mblist_info = {
153 	    sizeof(BalsaMBListClass),
154             NULL,               /* base_init */
155             NULL,               /* base_finalize */
156 	    (GClassInitFunc) bmbl_class_init,
157             NULL,               /* class_finalize */
158             NULL,               /* class_data */
159 	    sizeof(BalsaMBList),
160             0,                  /* n_preallocs */
161 	    (GInstanceInitFunc) bmbl_init
162 	};
163 
164 	mblist_type =
165             g_type_register_static(GTK_TYPE_TREE_VIEW,
166 	                           "BalsaMBList",
167                                    &mblist_info, 0);
168     }
169 
170     return mblist_type;
171 }
172 
173 
174 static void
bmbl_class_init(BalsaMBListClass * klass)175 bmbl_class_init(BalsaMBListClass * klass)
176 {
177     GObjectClass *object_class;
178     GtkWidgetClass *widget_class;
179 
180     parent_class = g_type_class_peek_parent(klass);
181 
182     object_class = (GObjectClass *) klass;
183     widget_class = (GtkWidgetClass *) klass;
184 
185     /* HAS_UNREAD_MAILBOX is emitted when the number of mailboxes with
186      * unread mail might have changed. */
187     balsa_mblist_signals[HAS_UNREAD_MAILBOX] =
188         g_signal_new("has-unread-mailbox",
189                      G_TYPE_FROM_CLASS(object_class),
190                      G_SIGNAL_RUN_FIRST,
191                      G_STRUCT_OFFSET(BalsaMBListClass, has_unread_mailbox),
192                      NULL, NULL,
193                      g_cclosure_marshal_VOID__INT,
194                      G_TYPE_NONE,
195                      1, G_TYPE_INT);
196 
197     /* GObject signals */
198     object_class->set_property = bmbl_set_property;
199     object_class->get_property = bmbl_get_property;
200 
201     /* GtkWidget signals */
202     widget_class->drag_motion = bmbl_drag_motion;
203     widget_class->popup_menu = bmbl_popup_menu;
204 
205     /* Signals */
206     klass->has_unread_mailbox = NULL;
207 
208     /* Properties */
209     g_object_class_install_property(object_class, PROP_SHOW_CONTENT_INFO,
210                                     g_param_spec_boolean
211                                     ("show_content_info", NULL, NULL,
212                                      FALSE, G_PARAM_READWRITE));
213 }
214 
215 static void
bmbl_set_property_node_style(GSList * list)216 bmbl_set_property_node_style(GSList * list)
217 {
218     GSList *l;
219     GtkTreePath *path;
220 
221     gdk_threads_enter();
222 
223     for (l = list; l; l = l->next) {
224         GtkTreeRowReference *reference = l->data;
225         path = gtk_tree_row_reference_get_path(reference);
226         if (path) {
227             GtkTreeModel *model;
228             GtkTreeIter iter;
229 
230             model = gtk_tree_row_reference_get_model(reference);
231             if (gtk_tree_model_get_iter(model, &iter, path))
232                 bmbl_node_style(model, &iter);
233             gtk_tree_path_free(path);
234         }
235         gtk_tree_row_reference_free(reference);
236     }
237 
238     g_slist_free(list);
239     gdk_threads_leave();
240 }
241 
242 static gboolean
bmbl_set_property_foreach_func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)243 bmbl_set_property_foreach_func(GtkTreeModel * model, GtkTreePath * path,
244                                GtkTreeIter * iter, gpointer data)
245 {
246     GSList **list = data;
247     *list =
248         g_slist_prepend(*list, gtk_tree_row_reference_new(model, path));
249     return FALSE;
250 }
251 
252 #define BALSA_MBLIST_DISPLAY_INFO "balsa-mblist-display-info"
253 static void
bmbl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)254 bmbl_set_property(GObject * object, guint prop_id,
255                           const GValue * value, GParamSpec * pspec)
256 {
257     BalsaMBList *mblist = BALSA_MBLIST(object);
258     GtkTreeView *tree_view = GTK_TREE_VIEW(object);
259     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
260     gboolean display_info;
261     GtkTreeViewColumn *column;
262     GSList *list = NULL;
263 
264     switch (prop_id) {
265     case PROP_SHOW_CONTENT_INFO:
266         display_info = g_value_get_boolean(value);
267         mblist->display_info = display_info;
268         gtk_tree_view_set_headers_visible(tree_view, display_info);
269         column = gtk_tree_view_get_column(tree_view, 0);
270         gtk_tree_view_column_set_sizing(column,
271                                         display_info ?
272                                         GTK_TREE_VIEW_COLUMN_FIXED :
273                                         GTK_TREE_VIEW_COLUMN_GROW_ONLY);
274         gtk_tree_view_column_set_resizable(column, display_info);
275         column = gtk_tree_view_get_column(tree_view, 1);
276         gtk_tree_view_column_set_visible(column, display_info);
277         column = gtk_tree_view_get_column(tree_view, 2);
278         gtk_tree_view_column_set_visible(column, display_info);
279         g_object_set_data(G_OBJECT(model), BALSA_MBLIST_DISPLAY_INFO,
280                           GINT_TO_POINTER(display_info));
281         gtk_tree_model_foreach(GTK_TREE_MODEL(balsa_app.mblist_tree_store),
282                                bmbl_set_property_foreach_func, &list);
283         bmbl_set_property_node_style(list);
284         break;
285 
286     default:
287         break;
288     }
289 }
290 
291 static void
bmbl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)292 bmbl_get_property(GObject * object, guint prop_id, GValue * value,
293                           GParamSpec * pspec)
294 {
295     BalsaMBList *mblist = BALSA_MBLIST(object);
296 
297     switch (prop_id) {
298     case PROP_SHOW_CONTENT_INFO:
299         g_value_set_boolean(value, mblist->display_info);
300         break;
301     default:
302         break;
303     }
304 }
305 
306 static gboolean
bmbl_drag_motion(GtkWidget * mblist,GdkDragContext * context,gint x,gint y,guint time)307 bmbl_drag_motion(GtkWidget * mblist, GdkDragContext * context, gint x,
308                  gint y, guint time)
309 {
310     GtkTreeView *tree_view = GTK_TREE_VIEW(mblist);
311     GtkTreePath *path;
312     GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
313     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
314     gboolean ret_val;
315     gboolean can_drop;
316 
317     ret_val =
318         GTK_WIDGET_CLASS(parent_class)->drag_motion(mblist, context, x, y,
319                                                     time);
320 
321     gtk_tree_view_get_drag_dest_row(tree_view, &path, NULL);
322     if (!path)
323         return FALSE;
324 
325     can_drop = bmbl_selection_func(selection, model, path, FALSE, NULL);
326     gtk_tree_view_set_drag_dest_row(tree_view, can_drop ? path : NULL,
327                                     GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
328     gtk_tree_path_free(path);
329 
330     gdk_drag_status(context,
331                     (gdk_drag_context_get_actions(context) ==
332                      GDK_ACTION_COPY) ? GDK_ACTION_COPY :
333                     GDK_ACTION_MOVE, time);
334 
335     return (ret_val && can_drop);
336 }
337 
338 /*
339  * Set up the mail box list, including the tree's appearance and the
340  * callbacks
341  */
342 static void
bmbl_init(BalsaMBList * mblist)343 bmbl_init(BalsaMBList * mblist)
344 {
345     GtkTreeStore *store = balsa_mblist_get_store();
346     GtkTreeView *tree_view = GTK_TREE_VIEW(mblist);
347     GtkCellRenderer *renderer;
348     GtkTreeViewColumn *column;
349 
350     gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(store));
351     g_object_unref(store);
352 
353     /* Mailbox icon and name go in first column. */
354     column = gtk_tree_view_column_new();
355     gtk_tree_view_column_set_title(column, _("Mailbox"));
356     gtk_tree_view_column_set_alignment(column, 0.5);
357     renderer = gtk_cell_renderer_pixbuf_new();
358     gtk_tree_view_column_pack_start(column, renderer, FALSE);
359     gtk_tree_view_column_set_attributes(column, renderer,
360                                         "icon-name", ICON_COLUMN,
361                                         NULL);
362     renderer = gtk_cell_renderer_text_new();
363     gtk_tree_view_column_pack_start(column, renderer, FALSE);
364     gtk_tree_view_column_set_attributes(column, renderer,
365                                         "text", NAME_COLUMN,
366                                         "weight", WEIGHT_COLUMN,
367                                         "style", STYLE_COLUMN,
368                                         NULL);
369     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
370     gtk_tree_view_column_set_fixed_width(column,
371                                          balsa_app.mblist_name_width);
372     gtk_tree_view_column_set_resizable(column, TRUE);
373     gtk_tree_view_append_column(tree_view, column);
374     gtk_tree_view_column_set_sort_column_id(column,
375                                             BMBL_TREE_COLUMN_NAME);
376      /* set sort column id will make the column clickable - disable that! */
377     gtk_tree_view_column_set_clickable(column, FALSE);
378 
379     /* Message counts are right-justified, each in a column centered
380      * under its heading. */
381     /* Unread message count column */
382     column = gtk_tree_view_column_new();
383     gtk_tree_view_column_set_title(column, "U");
384     gtk_tree_view_column_set_alignment(column, 0.5);
385     renderer = gtk_cell_renderer_text_new();
386     gtk_tree_view_column_pack_start(column, renderer, TRUE);
387     renderer = gtk_cell_renderer_text_new();
388     g_object_set(renderer, "xalign", 1.0, NULL);
389     gtk_tree_view_column_pack_start(column, renderer, FALSE);
390     gtk_tree_view_column_set_attributes(column, renderer,
391                                         "text", UNREAD_COLUMN,
392                                         NULL);
393     renderer = gtk_cell_renderer_text_new();
394     gtk_tree_view_column_pack_start(column, renderer, TRUE);
395     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
396     gtk_tree_view_column_set_fixed_width(column,
397                                          balsa_app.mblist_newmsg_width);
398     gtk_tree_view_column_set_resizable(column, TRUE);
399     gtk_tree_view_append_column(tree_view, column);
400 #ifdef SORTING_MAILBOX_LIST_IS_USEFUL
401     gtk_tree_view_column_set_sort_column_id(column,
402                                             BMBL_TREE_COLUMN_UNREAD);
403 #endif
404 
405     /* Total message count column */
406     column = gtk_tree_view_column_new();
407     gtk_tree_view_column_set_title(column, "T");
408     gtk_tree_view_column_set_alignment(column, 0.5);
409     renderer = gtk_cell_renderer_text_new();
410     gtk_tree_view_column_pack_start(column, renderer, TRUE);
411     renderer = gtk_cell_renderer_text_new();
412     g_object_set(renderer, "xalign", 1.0, NULL);
413     gtk_tree_view_column_pack_start(column, renderer, FALSE);
414     gtk_tree_view_column_set_attributes(column, renderer,
415                                         "text", TOTAL_COLUMN,
416                                         NULL);
417     renderer = gtk_cell_renderer_text_new();
418     gtk_tree_view_column_pack_start(column, renderer, TRUE);
419     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
420     gtk_tree_view_column_set_fixed_width(column,
421                                          balsa_app.mblist_totalmsg_width);
422     gtk_tree_view_column_set_resizable(column, TRUE);
423     gtk_tree_view_append_column(tree_view, column);
424 #ifdef SORTING_MAILBOX_LIST_IS_USEFUL
425     gtk_tree_view_column_set_sort_column_id(column,
426                                             BMBL_TREE_COLUMN_TOTAL);
427 #endif
428     /* arrange for non-mailbox nodes to be non-selectable */
429     gtk_tree_selection_set_select_function(gtk_tree_view_get_selection
430                                            (tree_view),
431                                            bmbl_selection_func, NULL,
432                                            NULL);
433 
434     g_signal_connect_after(G_OBJECT(tree_view), "row-expanded",
435                            G_CALLBACK(bmbl_tree_expand), NULL);
436     g_signal_connect(G_OBJECT(tree_view), "row-collapsed",
437                      G_CALLBACK(bmbl_tree_collapse), NULL);
438 
439     g_object_set(G_OBJECT(mblist),
440                  "show_content_info",
441                  balsa_app.mblist_show_mb_content_info,
442                  NULL);
443 
444     gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store),
445                                     BMBL_TREE_COLUMN_NAME,
446                                     bmbl_row_compare,
447                                     GINT_TO_POINTER(BMBL_TREE_COLUMN_NAME),
448                                     NULL);
449 #ifdef SORTING_MAILBOX_LIST_IS_USEFUL
450     gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store),
451                                     BMBL_TREE_COLUMN_UNREAD,
452                                     bmbl_row_compare,
453                                     GINT_TO_POINTER(BMBL_TREE_COLUMN_UNREAD),
454                                     NULL);
455     gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store),
456                                     BMBL_TREE_COLUMN_TOTAL,
457                                     bmbl_row_compare,
458                                     GINT_TO_POINTER(BMBL_TREE_COLUMN_TOTAL),
459                                     NULL);
460 #endif
461     /* Default is ascending sort by name */
462     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
463 					 BMBL_TREE_COLUMN_NAME,
464 					 GTK_SORT_ASCENDING);
465 }
466 
467 /*
468  * balsa_mblist_get_store
469  *
470  * Return balsa_app.mblist_tree_store, setting it up if this is the
471  * first time.
472  */
473 GtkTreeStore *
balsa_mblist_get_store(void)474 balsa_mblist_get_store(void)
475 {
476     if (balsa_app.mblist_tree_store)
477 	g_object_ref(balsa_app.mblist_tree_store);
478     else {
479         balsa_app.mblist_tree_store =
480             gtk_tree_store_new(N_COLUMNS,
481                                G_TYPE_OBJECT,     /* MBNODE_COLUMN */
482                                G_TYPE_STRING,     /* ICON_COLUMN   */
483                                G_TYPE_STRING,     /* NAME_COLUMN   */
484                                PANGO_TYPE_WEIGHT, /* WEIGHT_COLUMN */
485                                PANGO_TYPE_STYLE,  /* STYLE_COLUMN */
486                                G_TYPE_STRING,     /* UNREAD_COLUMN */
487                                G_TYPE_STRING      /* TOTAL_COLUMN  */
488             );
489         g_object_add_weak_pointer(G_OBJECT(balsa_app.mblist_tree_store),
490                                   (gpointer) & balsa_app.
491                                   mblist_tree_store);
492     }
493 
494     return balsa_app.mblist_tree_store;
495 }
496 
497 /*
498  * bmbl_selection_func
499  *
500  * Used to filter whether or not a row may be selected.
501  */
502 static gboolean
bmbl_selection_func(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean path_currently_selected,gpointer data)503 bmbl_selection_func(GtkTreeSelection * selection, GtkTreeModel * model,
504                       GtkTreePath * path, gboolean path_currently_selected,
505                       gpointer data)
506 {
507     GtkTreeIter iter;
508     BalsaMailboxNode *mbnode;
509     gboolean retval;
510 
511     gtk_tree_model_get_iter(model, &iter, path);
512     gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
513 
514     /* If the node is selected, allow it to be deselected, whether or
515      * not it has a mailbox (if it doesn't, it shouldn't have been
516      * selected in the first place, but you never know...). */
517     retval = (path_currently_selected || (mbnode && mbnode->mailbox));
518     g_object_unref(mbnode);
519     return retval;
520 }
521 
522 GtkWidget *
balsa_mblist_new()523 balsa_mblist_new()
524 {
525     BalsaMBList *new;
526 
527     new = g_object_new(balsa_mblist_get_type(), NULL);
528 
529     return GTK_WIDGET(new);
530 }
531 
532 /* callbacks */
533 
534 /* "row-expanded" */
535 
536 static void
bmbl_tree_expand(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path,gpointer data)537 bmbl_tree_expand(GtkTreeView * tree_view, GtkTreeIter * iter,
538                     GtkTreePath * path, gpointer data)
539 {
540     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
541     BalsaMailboxNode *mbnode;
542     GtkTreeIter child_iter;
543 
544     gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
545     balsa_mailbox_node_scan_children(mbnode);
546 
547     if (!mbnode->mailbox)
548         gtk_tree_store_set(GTK_TREE_STORE(model), iter,
549                            ICON_COLUMN,
550                            balsa_icon_id(BALSA_PIXMAP_MBOX_DIR_OPEN),
551                            -1);
552     g_object_unref(mbnode);
553 
554     if (gtk_tree_model_iter_children(model, &child_iter, iter)) {
555 	GtkWidget *current_index =
556 	    balsa_window_find_current_index(balsa_app.main_window);
557 	LibBalsaMailbox *current_mailbox =
558 	    current_index ?
559 	    BALSA_INDEX(current_index)->mailbox_node->mailbox :
560 	    NULL;
561 	gboolean first_mailbox = TRUE;
562 
563         do {
564             gtk_tree_model_get(model, &child_iter,
565                                MBNODE_COLUMN, &mbnode, -1);
566             if (mbnode && mbnode->mailbox) {
567 		/* Mark only one mailbox as exposed. */
568 		if (first_mailbox) {
569 		    libbalsa_mailbox_set_exposed(mbnode->mailbox, TRUE);
570 		    first_mailbox = FALSE;
571 		} else
572 		    libbalsa_mailbox_set_exposed(mbnode->mailbox, FALSE);
573 		if (mbnode->mailbox == current_mailbox) {
574 		    GtkTreeSelection *selection =
575 			gtk_tree_view_get_selection(tree_view);
576 		    g_signal_handlers_block_by_func(selection,
577 						    bmbl_select_mailbox,
578 						    NULL);
579 		    gtk_tree_selection_select_iter(selection, &child_iter);
580 		    g_signal_handlers_unblock_by_func(selection,
581 						      bmbl_select_mailbox,
582 						      NULL);
583 		}
584 	    }
585 	    g_object_unref(mbnode);
586         } while (gtk_tree_model_iter_next(model, &child_iter));
587     }
588 }
589 
590 /* "row-collapsed" */
591 static void
bmbl_tree_collapse_helper(GtkTreeModel * model,GtkTreeIter * iter)592 bmbl_tree_collapse_helper(GtkTreeModel * model, GtkTreeIter * iter)
593 {
594     GtkTreeIter child_iter;
595 
596     if (gtk_tree_model_iter_children(model, &child_iter, iter)) {
597         do {
598             BalsaMailboxNode *mbnode;
599 
600             gtk_tree_model_get(model, &child_iter,
601                                MBNODE_COLUMN, &mbnode, -1);
602             if (mbnode->mailbox)
603 		libbalsa_mailbox_set_exposed(mbnode->mailbox, FALSE);
604 	    g_object_unref(mbnode);
605             bmbl_tree_collapse_helper(model, &child_iter);
606         } while (gtk_tree_model_iter_next(model, &child_iter));
607     }
608 }
609 
610 static void
bmbl_tree_collapse(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path,gpointer data)611 bmbl_tree_collapse(GtkTreeView * tree_view, GtkTreeIter * iter,
612                       GtkTreePath * path, gpointer data)
613 {
614     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
615     BalsaMailboxNode *mbnode;
616 
617     gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
618 
619     if (!mbnode->mailbox)
620         gtk_tree_store_set(GTK_TREE_STORE(model), iter,
621                            ICON_COLUMN,
622                            balsa_icon_id(BALSA_PIXMAP_MBOX_DIR_CLOSED),
623                            -1);
624     g_object_unref(mbnode);
625 
626     bmbl_tree_collapse_helper(model, iter);
627 }
628 
629 /* bmbl_row_compare
630  *
631  * This function determines the sorting order of the list, depending
632  * on what column is selected.  The first column sorts by name, with
633  * exception given to the five "core" mailboxes (Inbox, Draftbox,
634  * Sentbox, Outbox, Trash).  The second sorts by number of unread
635  * messages, and the third by total number of messages.
636  * */
637 static gint
bmbl_row_compare(GtkTreeModel * model,GtkTreeIter * iter1,GtkTreeIter * iter2,gpointer data)638 bmbl_row_compare(GtkTreeModel * model, GtkTreeIter * iter1,
639                  GtkTreeIter * iter2, gpointer data)
640 {
641     BmblTreeColumnId sort_column = GPOINTER_TO_INT(data);
642     BalsaMailboxNode *mbnode;
643     LibBalsaMailbox *m1 = NULL;
644     LibBalsaMailbox *m2 = NULL;
645     gchar *name1, *name2;
646     gint core1;
647     gint core2;
648     gint ret_val = 0;
649 
650     gtk_tree_model_get(model, iter1,
651                        MBNODE_COLUMN, &mbnode, NAME_COLUMN, &name1, -1);
652     m1 = mbnode->mailbox;
653     g_object_unref(mbnode);
654 
655     gtk_tree_model_get(model, iter2,
656                        MBNODE_COLUMN, &mbnode, NAME_COLUMN, &name2, -1);
657     m2 = mbnode->mailbox;
658     g_object_unref(mbnode);
659 
660     switch (sort_column) {
661     case BMBL_TREE_COLUMN_NAME:
662         /* compare using names, potentially mailboxnodes */
663         core1 = bmbl_core_mailbox(m1);
664         core2 = bmbl_core_mailbox(m2);
665         ret_val = ((core1 || core2) ? core2 - core1
666                    : g_ascii_strcasecmp(name1, name2));
667         break;
668 
669     case BMBL_TREE_COLUMN_UNREAD:
670         ret_val = (libbalsa_mailbox_get_unread(m1)
671                    - libbalsa_mailbox_get_unread(m2));
672         break;
673 
674     case BMBL_TREE_COLUMN_TOTAL:
675         ret_val = (libbalsa_mailbox_get_total(m1)
676                    - libbalsa_mailbox_get_total(m2));
677         break;
678     }
679 
680     g_free(name1);
681     g_free(name2);
682     return ret_val;
683 }
684 
685 /* bmbl_button_press_cb:
686    handle mouse button press events that occur on mailboxes
687    (clicking on folders is passed to GtkTreeView and may trigger expand events
688 */
689 static gboolean
bmbl_button_press_cb(GtkWidget * widget,GdkEventButton * event,gpointer data)690 bmbl_button_press_cb(GtkWidget * widget, GdkEventButton * event,
691                      gpointer data)
692 {
693     GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
694     GtkTreePath *path;
695 
696     if (event->type != GDK_BUTTON_PRESS || event->button != 3
697         || event->window != gtk_tree_view_get_bin_window(tree_view))
698         return FALSE;
699 
700     if (!gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y,
701                                        &path, NULL, NULL, NULL))
702         path = NULL;
703     bmbl_do_popup(tree_view, path, event);
704     /* bmbl_do_popup frees path */
705 
706     return TRUE;
707 }
708 
709 /* bmbl_popup_menu:
710  * default handler for the "popup-menu" signal, which is issued when the
711  * user hits shift-F10
712  */
713 static gboolean
bmbl_popup_menu(GtkWidget * widget)714 bmbl_popup_menu(GtkWidget * widget)
715 {
716     GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
717     GtkTreePath *path;
718 
719     gtk_tree_view_get_cursor(tree_view, &path, NULL);
720     bmbl_do_popup(tree_view, path, NULL);
721     /* bmbl_do_popup frees path */
722     return TRUE;
723 }
724 
725 /* bmbl_do_popup:
726  * do the popup, and free the path
727  */
728 static void
bmbl_do_popup(GtkTreeView * tree_view,GtkTreePath * path,GdkEventButton * event)729 bmbl_do_popup(GtkTreeView * tree_view, GtkTreePath * path,
730               GdkEventButton * event)
731 {
732     BalsaMailboxNode *mbnode = NULL;
733     gint event_button;
734     guint event_time;
735     GtkWidget *menu;
736 
737     if (path) {
738         GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
739         GtkTreeIter iter;
740 
741         if (gtk_tree_model_get_iter(model, &iter, path))
742             gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
743         gtk_tree_path_free(path);
744     }
745 
746     if (event) {
747         event_button = event->button;
748         event_time = event->time;
749     } else {
750         event_button = 0;
751         event_time = gtk_get_current_event_time();
752     }
753 
754     menu = balsa_mailbox_node_get_context_menu(mbnode);
755     g_object_ref(menu);
756     g_object_ref_sink(menu);
757     gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
758 		   event_button, event_time);
759     g_object_unref(menu);
760 
761     if (mbnode)
762 	g_object_unref(mbnode);
763 }
764 
765 /* bmbl_column_resize [MBG]
766  *
767  * clist: The clist (in this case ctree), that is having it's columns resized.
768  * column: The column being resized
769  * size:  The new size of the column
770  * data:  The data passed on to the callback when it was connected (NULL)
771  *
772  * Description: This callback assigns the new column widths to the balsa_app,
773  * so they can be saved and restored between sessions.
774  * */
775 static void
bmbl_column_resize(GtkWidget * widget,GtkAllocation * allocation,gpointer data)776 bmbl_column_resize(GtkWidget * widget,
777                            GtkAllocation * allocation, gpointer data)
778 {
779     GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
780     gint name_width =
781         gtk_tree_view_column_get_width(gtk_tree_view_get_column
782                                        (tree_view, 0));
783     gint newmsg_width =
784         gtk_tree_view_column_get_width(gtk_tree_view_get_column
785                                        (tree_view, 1));
786     gint totalmsg_width =
787         gtk_tree_view_column_get_width(gtk_tree_view_get_column
788                                        (tree_view, 2));
789 
790     if (name_width > 0 && newmsg_width > 0 && totalmsg_width > 0) {
791         balsa_app.mblist_name_width = name_width;
792         balsa_app.mblist_newmsg_width = newmsg_width;
793         balsa_app.mblist_totalmsg_width = totalmsg_width;
794     }
795 }
796 
797 /* bmbl_drag_cb
798  *
799  * Description: This is the drag_data_recieved signal handler for the
800  * BalsaMBList.  It retrieves the source BalsaIndex and transfers the
801  * index's selected messages to the target
802  * mailbox.  Depending on what key is held down when the message(s)
803  * are dropped they are either copied or moved.  The default action is
804  * to copy.
805  * */
806 static void
bmbl_drag_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint32 time,gpointer data)807 bmbl_drag_cb(GtkWidget * widget, GdkDragContext * context,
808              gint x, gint y, GtkSelectionData * selection_data,
809              guint info, guint32 time, gpointer data)
810 {
811     GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
812     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
813     GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
814     GtkTreePath *path;
815     GtkTreeIter iter;
816     LibBalsaMailbox *mailbox;
817     LibBalsaMailbox *orig_mailbox;
818     BalsaIndex *orig_index;
819     GArray *selected;
820 
821     if (!selection_data || !gtk_selection_data_get_data(selection_data))
822 	/* Drag'n'drop is weird... */
823 	return;
824 
825     orig_index =
826         *(BalsaIndex **) gtk_selection_data_get_data(selection_data);
827     selected = balsa_index_selected_msgnos_new(orig_index);
828     if (selected->len == 0) {
829 	/* it is actually possible to drag from GtkTreeView when no rows
830 	 * are selected: Disable preview for that. */
831         balsa_index_selected_msgnos_free(orig_index, selected);
832         return;
833     }
834 
835     orig_mailbox = orig_index->mailbox_node->mailbox;
836 
837     /* find the node and mailbox */
838 
839     /* we should be able to use:
840      * gtk_tree_view_get_drag_dest_row(tree_view, &path, NULL);
841      * but it sets path to NULL for some reason, so we'll go down to a
842      * lower level. */
843     if (gtk_tree_view_get_dest_row_at_pos(tree_view,
844                                           x, y, &path, NULL)) {
845 	BalsaMailboxNode *mbnode;
846 
847         gtk_tree_model_get_iter(model, &iter, path);
848         gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
849         mailbox = mbnode->mailbox;
850 	g_object_unref(mbnode);
851 
852         /* cannot transfer to the originating mailbox */
853         if (mailbox != NULL && mailbox != orig_mailbox)
854             balsa_index_transfer(orig_index, selected, mailbox,
855                                  gdk_drag_context_get_selected_action
856                                  (context) != GDK_ACTION_MOVE);
857         gtk_tree_path_free(path);
858     }
859     balsa_index_selected_msgnos_free(orig_index, selected);
860 
861     if (balsa_find_iter_by_data(&iter, orig_mailbox))
862         gtk_tree_selection_select_iter(selection, &iter);
863 }
864 
865 /* bmbl_select_mailbox
866  *
867  * This function is called when the user clicks on the mailbox list,
868  * to open the mailbox. It's also called if the user uses the keyboard
869  * to focus on the mailbox, in which case we don't open the mailbox.
870  */
871 static void
bmbl_select_mailbox(GtkTreeSelection * selection,gpointer data)872 bmbl_select_mailbox(GtkTreeSelection * selection, gpointer data)
873 {
874     GdkEvent *event = gtk_get_current_event();
875     GtkTreeIter iter;
876     GtkTreeView *tree_view =
877         gtk_tree_selection_get_tree_view(selection);
878     GtkTreeModel *model =
879         gtk_tree_view_get_model(tree_view);
880     GtkTreePath *path;
881 
882     if (!event) {
883 	GtkTreeModel *model;
884 	GtkTreeIter iter;
885 
886 	if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
887 	    BalsaMailboxNode *mbnode;
888 	    LibBalsaMailbox *mailbox;
889 	    gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
890 	    mailbox = mbnode->mailbox;
891 	    g_object_unref(mbnode);
892 	    if (MAILBOX_OPEN(mailbox))
893 		/* Opening a mailbox under program control. */
894 		return;
895 	}
896 	/* Not opening a mailbox--must be the initial selection of the
897 	 * first mailbox in the list, so we'll unselect it again. */
898 	g_signal_handlers_block_by_func(selection, bmbl_select_mailbox, NULL);
899 	gtk_tree_selection_unselect_all(selection);
900 	g_signal_handlers_unblock_by_func(selection, bmbl_select_mailbox, NULL);
901         return;
902     }
903     if (event->type != GDK_BUTTON_PRESS
904             /* keyboard navigation */
905         || event->button.button != 1
906             /* soft select */
907         || event->button.window != gtk_tree_view_get_bin_window(tree_view)
908             /* click on a different widget */ ) {
909         gdk_event_free(event);
910         return;
911     }
912 
913     if (!gtk_tree_view_get_path_at_pos(tree_view, event->button.x,
914                                        event->button.y, &path,
915                                        NULL, NULL, NULL)) {
916         /* GtkTreeView selects the first node in the tree when the
917          * widget first gets the focus, whether it's a keyboard event or
918          * a button event. If it's a button event, but no mailbox was
919          * clicked, we'll just undo that selection and return. */
920 	g_signal_handlers_block_by_func(selection, bmbl_select_mailbox, NULL);
921         gtk_tree_selection_unselect_all(selection);
922 	g_signal_handlers_unblock_by_func(selection, bmbl_select_mailbox, NULL);
923         gdk_event_free(event);
924         return;
925     }
926 
927     if (gtk_tree_selection_path_is_selected(selection, path)) {
928         BalsaMailboxNode *mbnode;
929 
930         gtk_tree_model_get_iter(model, &iter, path);
931         gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
932         g_return_if_fail(mbnode != NULL);
933 
934         if (mbnode->mailbox)
935             balsa_mblist_open_mailbox(mbnode->mailbox);
936 	g_object_unref(mbnode);
937     }
938     gtk_tree_path_free(path);
939     gdk_event_free(event);
940 }
941 
942 /*
943  * bmbl_row_activated_cb: callback for the "row-activated" signal
944  *
945  * This is emitted when focus is on a mailbox, and the user hits the
946  * space bar. It's also emitted if the user double-clicks on a mailbox,
947  * in which case we've already opened the mailbox. We could detect this
948  * by looking at gtk_current_event, but we don't want the extra code.
949  */
950 static void
bmbl_row_activated_cb(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer data)951 bmbl_row_activated_cb(GtkTreeView * tree_view, GtkTreePath * path,
952                       GtkTreeViewColumn * column, gpointer data)
953 {
954     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
955     GtkTreeIter iter;
956     BalsaMailboxNode *mbnode;
957 
958     gtk_tree_model_get_iter(model, &iter, path);
959     gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
960     g_return_if_fail(mbnode != NULL);
961 
962     if (mbnode->mailbox)
963         balsa_mblist_open_mailbox(mbnode->mailbox);
964     g_object_unref(mbnode);
965 }
966 
967 /* Mailbox status changed callbacks: update the UI in an idle handler.
968  */
969 
970 struct update_mbox_data {
971     LibBalsaMailbox *mailbox;
972     gboolean notify;
973 };
974 static void bmbl_update_mailbox(GtkTreeStore * store,
975                                 LibBalsaMailbox * mailbox);
976 
977 G_LOCK_DEFINE_STATIC(mblist_update);
978 
979 static gboolean
update_mailbox_idle(struct update_mbox_data * umd)980 update_mailbox_idle(struct update_mbox_data *umd)
981 {
982     G_LOCK(mblist_update);
983 
984     if (umd->mailbox) {
985         g_object_remove_weak_pointer(G_OBJECT(umd->mailbox),
986                                      (gpointer) & umd->mailbox);
987         g_object_set_data(G_OBJECT(umd->mailbox), "mblist-update", NULL);
988 
989         if (balsa_app.mblist_tree_store) {
990             gboolean subscribed =
991                 libbalsa_mailbox_get_subscribe(umd->mailbox) !=
992                 LB_MAILBOX_SUBSCRIBE_NO;
993             bmbl_update_mailbox(balsa_app.mblist_tree_store, umd->mailbox);
994             check_new_messages_count(umd->mailbox, umd->notify
995                                      && subscribed);
996 
997             if (subscribed) {
998                 if (libbalsa_mailbox_get_unread(umd->mailbox) > 0)
999                     g_signal_emit(balsa_app.mblist,
1000                                   balsa_mblist_signals[HAS_UNREAD_MAILBOX],
1001                                   0, TRUE);
1002                 else {
1003                     GList *unread_mailboxes =
1004                         balsa_mblist_find_all_unread_mboxes(NULL);
1005                     if (unread_mailboxes)
1006                         g_list_free(unread_mailboxes);
1007                     else
1008                         g_signal_emit(balsa_app.mblist,
1009                                       balsa_mblist_signals
1010                                       [HAS_UNREAD_MAILBOX], 0, FALSE);
1011                 }
1012             }
1013         }
1014     }
1015     g_free(umd);
1016 
1017     G_UNLOCK(mblist_update);
1018 
1019     return FALSE;
1020 }
1021 
1022 static void
bmbl_mailbox_changed_cb(LibBalsaMailbox * mailbox,gpointer data)1023 bmbl_mailbox_changed_cb(LibBalsaMailbox * mailbox, gpointer data)
1024 {
1025     struct update_mbox_data *umd;
1026 
1027     g_return_if_fail(LIBBALSA_IS_MAILBOX(mailbox));
1028 
1029     G_LOCK(mblist_update);
1030 
1031     umd = g_object_get_data(G_OBJECT(mailbox), "mblist-update");
1032 
1033     if (!umd) {
1034         umd = g_new(struct update_mbox_data, 1);
1035         g_object_set_data(G_OBJECT(mailbox), "mblist-update", umd);
1036         umd->mailbox = mailbox;
1037         g_object_add_weak_pointer(G_OBJECT(mailbox),
1038                                   (gpointer) & umd->mailbox);
1039         g_idle_add((GSourceFunc) update_mailbox_idle, umd);
1040     }
1041 
1042     umd->notify = (mailbox->state == LB_MAILBOX_STATE_OPEN
1043                    || mailbox->state == LB_MAILBOX_STATE_CLOSED);
1044 
1045     G_UNLOCK(mblist_update);
1046 }
1047 
1048 /* public methods */
1049 
1050 /* Caller must unref mbnode. */
1051 BalsaMailboxNode *
balsa_mblist_get_selected_node(BalsaMBList * mbl)1052 balsa_mblist_get_selected_node(BalsaMBList * mbl)
1053 {
1054     GtkTreeSelection *select =
1055         gtk_tree_view_get_selection(GTK_TREE_VIEW(mbl));
1056     BalsaMailboxNode *mbnode = NULL;
1057     GtkTreeModel *model;
1058     GtkTreeIter iter;
1059 
1060     if (gtk_tree_selection_get_selected(select, &model, &iter))
1061         gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
1062 
1063     return mbnode;
1064 }
1065 
1066 /* mblist_find_all_unread_mboxes:
1067  * If mailbox == NULL, returns a list of all mailboxes with unread mail.
1068  * If mailbox != NULL, returns a similar list, but including a NULL
1069  * marker for the position of mailbox in the list.
1070  * Trashbox is excluded.
1071  */
1072 struct bmbl_find_all_unread_mboxes_info {
1073     LibBalsaMailbox *mailbox;
1074     GList *list;
1075 };
1076 
1077 static gboolean
bmbl_find_all_unread_mboxes_func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1078 bmbl_find_all_unread_mboxes_func(GtkTreeModel * model, GtkTreePath * path,
1079                                  GtkTreeIter * iter, gpointer data)
1080 {
1081     struct bmbl_find_all_unread_mboxes_info *info = data;
1082     BalsaMailboxNode *mbnode = NULL;
1083     LibBalsaMailbox *mailbox;
1084 
1085     gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
1086     if(!mbnode) /* this node has no MBNODE associated at this time */
1087         return FALSE;
1088     mailbox = mbnode->mailbox;
1089     g_object_unref(mbnode);
1090 
1091     if (mailbox
1092         && (libbalsa_mailbox_get_subscribe(mailbox) !=
1093             LB_MAILBOX_SUBSCRIBE_NO)) {
1094         if (mailbox == info->mailbox)
1095             info->list = g_list_prepend(info->list, NULL);
1096         else if (libbalsa_mailbox_get_unread(mailbox) > 0)
1097             info->list = g_list_prepend(info->list, mailbox);
1098     }
1099 
1100     return FALSE;
1101 }
1102 
1103 GList *
balsa_mblist_find_all_unread_mboxes(LibBalsaMailbox * mailbox)1104 balsa_mblist_find_all_unread_mboxes(LibBalsaMailbox * mailbox)
1105 {
1106     struct bmbl_find_all_unread_mboxes_info info;
1107 
1108     info.mailbox = mailbox;
1109     info.list = NULL;
1110 
1111     if (!balsa_app.mblist_tree_store) /* We have no mailboxes, maybe
1112 					 we are about to quit? */
1113 	return NULL;
1114 
1115     gtk_tree_model_foreach(GTK_TREE_MODEL(balsa_app.mblist_tree_store),
1116                            bmbl_find_all_unread_mboxes_func, &info);
1117 
1118     return g_list_reverse(info.list);
1119 }
1120 
1121 /* mblist_open_mailbox
1122  *
1123  * Description: This checks to see if the mailbox is already on a different
1124  * mailbox page, or if a new page needs to be created and the mailbox
1125  * parsed.
1126  */
1127 static void
bmbl_open_mailbox(LibBalsaMailbox * mailbox,gboolean set_current)1128 bmbl_open_mailbox(LibBalsaMailbox * mailbox, gboolean set_current)
1129 {
1130     int i;
1131     GtkWidget *index;
1132     BalsaMailboxNode *mbnode;
1133 
1134     mbnode = balsa_find_mailbox(mailbox);
1135     if (!mbnode) {
1136         g_warning(_("Failed to find mailbox"));
1137         return;
1138     }
1139 
1140     index = balsa_window_find_current_index(balsa_app.main_window);
1141 
1142     /* If we currently have a page open, update the time last visited */
1143     if (index) {
1144 	time(&BALSA_INDEX(index)->mailbox_node->last_use);
1145     }
1146 
1147     i = balsa_find_notebook_page_num(mailbox);
1148     if (i != -1) {
1149         if (set_current) {
1150             gtk_notebook_set_current_page(GTK_NOTEBOOK(balsa_app.notebook),
1151                                           i);
1152             index = balsa_window_find_current_index(balsa_app.main_window);
1153             time(&BALSA_INDEX(index)->mailbox_node->last_use);
1154             balsa_index_set_column_widths(BALSA_INDEX(index));
1155         }
1156     } else { /* page with mailbox not found, open it */
1157         balsa_window_open_mbnode(balsa_app.main_window, mbnode,
1158                                  set_current);
1159 
1160 	if (balsa_app.mblist->display_info)
1161 	    balsa_mblist_update_mailbox(balsa_app.mblist_tree_store,
1162                                         mailbox);
1163     }
1164     g_object_unref(mbnode);
1165 }
1166 
1167 void
balsa_mblist_open_mailbox(LibBalsaMailbox * mailbox)1168 balsa_mblist_open_mailbox(LibBalsaMailbox * mailbox)
1169 {
1170     bmbl_open_mailbox(mailbox, TRUE);
1171 }
1172 
1173 void
balsa_mblist_open_mailbox_hidden(LibBalsaMailbox * mailbox)1174 balsa_mblist_open_mailbox_hidden(LibBalsaMailbox * mailbox)
1175 {
1176     bmbl_open_mailbox(mailbox, FALSE);
1177 }
1178 
1179 void
balsa_mblist_close_mailbox(LibBalsaMailbox * mailbox)1180 balsa_mblist_close_mailbox(LibBalsaMailbox * mailbox)
1181 {
1182     BalsaMailboxNode *mbnode;
1183 
1184     mbnode = balsa_find_mailbox(mailbox);
1185     if (!mbnode)  {
1186         g_warning(_("Failed to find mailbox"));
1187         return;
1188     }
1189 
1190     balsa_window_close_mbnode(balsa_app.main_window, mbnode);
1191     g_object_unref(mbnode);
1192 }
1193 
1194 /* balsa_mblist_close_lru_peer_mbx closes least recently used mailbox
1195  * on the same server as the one given as the argument: some IMAP
1196  * servers limit the number of simultaneously open connections. */
1197 struct lru_data {
1198     GtkTreePath     *ancestor_path;
1199     BalsaMailboxNode *mbnode;
1200 };
1201 
1202 static gboolean
get_lru_descendant(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1203 get_lru_descendant(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
1204                    gpointer data)
1205 {
1206     struct lru_data *dt  = (struct lru_data*)data;
1207     BalsaMailboxNode *mbnode;
1208 
1209     if(!gtk_tree_path_is_descendant(path, dt->ancestor_path))
1210         return FALSE;
1211     gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
1212     if(mbnode->mailbox && libbalsa_mailbox_is_open(mbnode->mailbox) &&
1213        (!dt->mbnode || (mbnode->last_use < dt->mbnode->last_use)) )
1214     {
1215         if (dt->mbnode)
1216             g_object_unref(dt->mbnode);
1217         dt->mbnode = mbnode;
1218     }
1219 
1220     else g_object_unref(mbnode);
1221     return FALSE;
1222 }
1223 
1224 gboolean
balsa_mblist_close_lru_peer_mbx(BalsaMBList * mblist,LibBalsaMailbox * mailbox)1225 balsa_mblist_close_lru_peer_mbx(BalsaMBList * mblist,
1226                                 LibBalsaMailbox *mailbox)
1227 {
1228     GtkTreeModel *model;
1229     GtkTreeIter   iter;
1230     struct lru_data dt;
1231     g_return_val_if_fail(mailbox, FALSE);
1232 
1233     model = gtk_tree_view_get_model(GTK_TREE_VIEW(mblist));
1234 
1235     if(!balsa_find_iter_by_data(&iter, mailbox))
1236         return FALSE;
1237     dt.ancestor_path = gtk_tree_model_get_path(model, &iter);
1238     while(gtk_tree_path_get_depth(dt.ancestor_path)>1)
1239         gtk_tree_path_up(dt.ancestor_path);
1240 
1241     dt.mbnode = NULL;
1242     gtk_tree_model_foreach(model, get_lru_descendant, &dt);
1243     if(dt.mbnode) {
1244         gdk_threads_enter();
1245         balsa_window_close_mbnode(balsa_app.main_window, dt.mbnode);
1246         gdk_threads_leave();
1247         g_object_unref(dt.mbnode);
1248     }
1249     return dt.mbnode != NULL;
1250 }
1251 
1252 /* balsa_mblist_default_signal_bindings:
1253    connect signals useful for the left-hand side mailbox tree
1254    but useless for the transfer menu.
1255 */
1256 void
balsa_mblist_default_signal_bindings(BalsaMBList * mblist)1257 balsa_mblist_default_signal_bindings(BalsaMBList * mblist)
1258 {
1259     GtkTreeSelection *selection;
1260 
1261     g_signal_connect(G_OBJECT(mblist), "button_press_event",
1262                      G_CALLBACK(bmbl_button_press_cb), NULL);
1263     g_signal_connect_after(G_OBJECT(mblist), "size-allocate",
1264                            G_CALLBACK(bmbl_column_resize), NULL);
1265     gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(mblist),
1266                                          bmbl_drop_types,
1267                                          ELEMENTS(bmbl_drop_types),
1268                                          GDK_ACTION_DEFAULT |
1269                                          GDK_ACTION_COPY |
1270                                          GDK_ACTION_MOVE);
1271     g_signal_connect(G_OBJECT(mblist), "drag-data-received",
1272                      G_CALLBACK(bmbl_drag_cb), NULL);
1273 
1274     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mblist));
1275     g_signal_connect(G_OBJECT(selection), "changed",
1276                      G_CALLBACK(bmbl_select_mailbox), NULL);
1277     g_signal_connect(G_OBJECT(mblist), "row-activated",
1278                      G_CALLBACK(bmbl_row_activated_cb), NULL);
1279 }
1280 
1281 /* bmbl_disconnect_mailbox_signals
1282  *
1283  * Remove the signals we attached to the mailboxes.
1284  */
1285 static void
bmbl_real_disconnect_mbnode_signals(BalsaMailboxNode * mbnode,GtkTreeModel * model)1286 bmbl_real_disconnect_mbnode_signals(BalsaMailboxNode * mbnode,
1287 				    GtkTreeModel * model)
1288 {
1289     if (mbnode->mailbox)
1290         g_signal_handlers_disconnect_by_func(mbnode->mailbox,
1291                                              G_CALLBACK
1292                                              (bmbl_mailbox_changed_cb),
1293                                              NULL);
1294 }
1295 
1296 /* bmbl_store_redraw_mbnode
1297  *
1298  * adds BalsaMailboxNodes to the mailbox list, choosing proper icon for them.
1299  * returns FALSE on failure (wrong parameters passed).
1300  * */
1301 static gboolean
bmbl_store_redraw_mbnode(GtkTreeIter * iter,BalsaMailboxNode * mbnode)1302 bmbl_store_redraw_mbnode(GtkTreeIter * iter, BalsaMailboxNode * mbnode)
1303 {
1304     const gchar *icon;
1305     const gchar *name;
1306     gchar *tmp = NULL;
1307     gboolean expose = FALSE;
1308 
1309     g_return_val_if_fail(mbnode, FALSE);
1310 
1311     if (mbnode->mailbox) {
1312         LibBalsaMailbox *mailbox = mbnode->mailbox;
1313 	static guint mailbox_changed_signal = 0;
1314 
1315 	if (LIBBALSA_IS_MAILBOX_POP3(mailbox)) {
1316 	    g_assert_not_reached();
1317             icon = NULL;
1318             name = NULL;
1319         } else {
1320 	    if(mailbox == balsa_app.draftbox)
1321 		icon = BALSA_PIXMAP_MBOX_DRAFT;
1322 	    else if(mailbox == balsa_app.inbox)
1323 		icon = BALSA_PIXMAP_MBOX_IN;
1324 	    else if(mailbox == balsa_app.outbox)
1325 		icon = BALSA_PIXMAP_MBOX_OUT;
1326 	    else if(mailbox == balsa_app.sentbox)
1327 		icon = BALSA_PIXMAP_MBOX_SENT;
1328 	    else if(mailbox == balsa_app.trash)
1329 		icon = BALSA_PIXMAP_MBOX_TRASH;
1330 	    else
1331 		icon = (libbalsa_mailbox_total_messages(mailbox) > 0)
1332 		? BALSA_PIXMAP_MBOX_TRAY_FULL
1333                 : BALSA_PIXMAP_MBOX_TRAY_EMPTY;
1334 
1335             name = mailbox->name;
1336 
1337             /* Make sure the show column is set before showing the
1338              * mailbox in the list. */
1339 	    if (libbalsa_mailbox_get_show(mailbox) == LB_MAILBOX_SHOW_UNSET)
1340 		libbalsa_mailbox_set_show(mailbox,
1341 					  (mailbox == balsa_app.sentbox
1342 					   || mailbox == balsa_app.draftbox
1343 					   || mailbox == balsa_app.outbox) ?
1344                                           LB_MAILBOX_SHOW_TO :
1345                                           LB_MAILBOX_SHOW_FROM);
1346             /* ...and whether to jump to new mail in this mailbox. */
1347             if (libbalsa_mailbox_get_subscribe(mailbox) ==
1348                 LB_MAILBOX_SUBSCRIBE_UNSET)
1349                 libbalsa_mailbox_set_subscribe(mailbox,
1350                                                mailbox ==
1351                                                balsa_app.trash ?
1352                                                LB_MAILBOX_SUBSCRIBE_NO :
1353                                                LB_MAILBOX_SUBSCRIBE_YES);
1354 	}
1355 
1356 	if (!mailbox_changed_signal)
1357 	    mailbox_changed_signal =
1358 		g_signal_lookup("changed", LIBBALSA_TYPE_MAILBOX);
1359 	if (!g_signal_has_handler_pending(G_OBJECT(mbnode->mailbox),
1360                                           mailbox_changed_signal, 0, TRUE)) {
1361 	    /* Now we have a mailbox: */
1362 	    g_signal_connect(mbnode->mailbox, "changed",
1363 			     G_CALLBACK(bmbl_mailbox_changed_cb),
1364 			     NULL);
1365             if (libbalsa_mailbox_get_unread(mailbox) > 0
1366                 && (libbalsa_mailbox_get_subscribe(mailbox) !=
1367                     LB_MAILBOX_SUBSCRIBE_NO))
1368                 g_signal_emit(balsa_app.mblist,
1369                               balsa_mblist_signals[HAS_UNREAD_MAILBOX],
1370                               0, TRUE);
1371 	    /* If necessary, expand rows to expose this mailbox after
1372 	     * setting its mbnode in the tree-store. */
1373 	    expose = libbalsa_mailbox_get_exposed(mbnode->mailbox);
1374 	}
1375     } else {
1376 	/* new directory, but not a mailbox */
1377 	icon = BALSA_PIXMAP_MBOX_DIR_CLOSED;
1378         name = tmp = g_path_get_basename(mbnode->name);
1379     }
1380 
1381     gtk_tree_store_set(balsa_app.mblist_tree_store, iter,
1382                        MBNODE_COLUMN, mbnode,
1383                        ICON_COLUMN, balsa_icon_id(icon),
1384                        NAME_COLUMN,   name,
1385                        WEIGHT_COLUMN, PANGO_WEIGHT_NORMAL,
1386                        STYLE_COLUMN, PANGO_STYLE_NORMAL,
1387                        UNREAD_COLUMN, "",
1388                        TOTAL_COLUMN,  "",
1389                        -1);
1390     g_free(tmp);
1391 
1392     if (mbnode->mailbox) {
1393 	GtkTreeModel *model = GTK_TREE_MODEL(balsa_app.mblist_tree_store);
1394 	if (expose) {
1395 	    GtkTreePath *path = gtk_tree_model_get_path(model, iter);
1396 	    bmbl_expand_to_row(balsa_app.mblist, path);
1397 	    gtk_tree_path_free(path);
1398 	}
1399 	bmbl_node_style(model, iter);
1400     }
1401 
1402     return TRUE;
1403 }
1404 
1405 /* balsa_mblist_update_mailbox [MBG]
1406  *
1407  * mblist: the mailbox list that contains the mailbox
1408  * mbnode:  the mailbox node that you wish to update
1409  *
1410  * Description: the function looks for the mailbox in the mblist, if
1411  * it's there it changes the style (and fills the info columns)
1412  * depending on the mailbox variables unread_messages and
1413  * total_messages. Selects the mailbox (important when previous mailbox
1414  * was closed).
1415  * */
1416 static void
bmbl_update_mailbox(GtkTreeStore * store,LibBalsaMailbox * mailbox)1417 bmbl_update_mailbox(GtkTreeStore * store, LibBalsaMailbox * mailbox)
1418 {
1419     GtkTreeModel *model = GTK_TREE_MODEL(store);
1420     GtkTreeIter iter;
1421     GtkWidget *bindex;
1422 
1423     /* try and find the mailbox */
1424     if (!balsa_find_iter_by_data(&iter, mailbox))
1425         return;
1426 
1427     bmbl_node_style(model, &iter);
1428 
1429     bindex = balsa_window_find_current_index(balsa_app.main_window);
1430     if (!bindex || mailbox != BALSA_INDEX(bindex)->mailbox_node->mailbox)
1431         return;
1432 
1433     balsa_window_set_statusbar(balsa_app.main_window, mailbox);
1434 }
1435 
1436 void
balsa_mblist_update_mailbox(GtkTreeStore * store,LibBalsaMailbox * mailbox)1437 balsa_mblist_update_mailbox(GtkTreeStore * store,
1438 			    LibBalsaMailbox * mailbox)
1439 {
1440     bmbl_update_mailbox(store, mailbox);
1441 }
1442 
1443 /* bmbl_node_style [MBG]
1444  *
1445  * model:  The model containing the mailbox
1446  * iter : the iterator pointing on the mailbox node
1447  * Description: A function to actually do the changing of the style,
1448  * and is called by both balsa_mblist_update_mailbox, and
1449  * balsa_mblist_check_new
1450  *
1451  * NOTES: ignore special mailboxes.
1452  * */
1453 static void
bmbl_node_style(GtkTreeModel * model,GtkTreeIter * iter)1454 bmbl_node_style(GtkTreeModel * model, GtkTreeIter * iter)
1455 {
1456     BalsaMailboxNode * mbnode;
1457     LibBalsaMailbox *mailbox;
1458     gint unread_messages;
1459     gint total_messages;
1460     GtkTreeIter parent;
1461     gboolean has_unread_child;
1462     gchar *text_unread = NULL;
1463     gchar *text_total = NULL;
1464 
1465     gtk_tree_model_get(model, iter, MBNODE_COLUMN, &mbnode, -1);
1466     if (!mbnode || !mbnode->mailbox)
1467         return;
1468 
1469     mailbox = mbnode->mailbox;
1470     unread_messages = libbalsa_mailbox_get_unread(mailbox);
1471     total_messages = libbalsa_mailbox_get_total(mailbox);
1472 
1473     /* SHOW UNREAD for special mailboxes? */
1474     if (!(mailbox == balsa_app.sentbox || mailbox == balsa_app.outbox ||
1475           mailbox == balsa_app.draftbox || mailbox == balsa_app.trash)) {
1476         const gchar *icon;
1477         const gchar *name;
1478         gchar *tmp = NULL;
1479         PangoWeight weight;
1480 
1481         /* Set the style appropriate for unread_messages; we do this
1482          * even if the state hasn't changed, because we might be
1483          * rerendering after hiding or showing the info columns. */
1484         if (unread_messages > 0) {
1485             gboolean display_info;
1486 
1487             icon = BALSA_PIXMAP_MBOX_TRAY_FULL;
1488 
1489             display_info = GPOINTER_TO_INT(g_object_get_data
1490                                            (G_OBJECT(model),
1491                                             BALSA_MBLIST_DISPLAY_INFO));
1492             name = (!display_info && total_messages >= 0) ?
1493                 (tmp = g_strdup_printf("%s (%d)", mailbox->name,
1494                                       unread_messages))
1495                 : mailbox->name;
1496 
1497             weight = PANGO_WEIGHT_BOLD;
1498             mbnode->style |= MBNODE_STYLE_NEW_MAIL;
1499         } else {
1500             icon = (mailbox == balsa_app.inbox) ?
1501                 BALSA_PIXMAP_MBOX_IN : BALSA_PIXMAP_MBOX_TRAY_EMPTY;
1502             name = mailbox->name;
1503             weight = PANGO_WEIGHT_NORMAL;
1504             mbnode->style &= ~MBNODE_STYLE_NEW_MAIL;
1505         }
1506 
1507         gtk_tree_store_set(GTK_TREE_STORE(model), iter,
1508                            ICON_COLUMN, balsa_icon_id(icon),
1509                            NAME_COLUMN, name,
1510                            WEIGHT_COLUMN, weight,
1511                            -1);
1512         g_free(tmp);
1513 
1514     }
1515     g_object_unref(mbnode);
1516 
1517     if (total_messages >= 0) {
1518         /* Both counts are valid. */
1519         text_unread = g_strdup_printf("%d", unread_messages);
1520         text_total  = g_strdup_printf("%d", total_messages);
1521     } else if (unread_messages == 0)
1522         /* Total is unknown, and unread is unknown unless it's 0. */
1523         text_unread = g_strdup("0");
1524     gtk_tree_store_set(GTK_TREE_STORE(model), iter,
1525                        UNREAD_COLUMN, text_unread,
1526                        TOTAL_COLUMN, text_total,
1527                        -1);
1528     g_free(text_unread);
1529     g_free(text_total);
1530 
1531     /* Do the folder styles as well */
1532     has_unread_child = libbalsa_mailbox_get_unread(mailbox) > 0;
1533     while (gtk_tree_model_iter_parent(model, &parent, iter)) {
1534 	*iter = parent;
1535 	gtk_tree_model_get(model, &parent, MBNODE_COLUMN, &mbnode, -1);
1536 	if (!has_unread_child) {
1537 	    /* Check all the children of this parent. */
1538 	    GtkTreeIter child;
1539 	    /* We know it has at least one child. */
1540 	    gtk_tree_model_iter_children(model, &child, &parent);
1541 	    do {
1542 		BalsaMailboxNode *mn;
1543 		gtk_tree_model_get(model, &child, MBNODE_COLUMN, &mn, -1);
1544 		if (mn->style & (MBNODE_STYLE_NEW_MAIL |
1545 				 MBNODE_STYLE_UNREAD_CHILD))
1546 		    has_unread_child = TRUE;
1547 		g_object_unref(mn);
1548 	    } while (!has_unread_child && gtk_tree_model_iter_next(model, &child));
1549 	}
1550 	if (has_unread_child) {
1551 	    mbnode->style |= MBNODE_STYLE_UNREAD_CHILD;
1552 	    gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
1553 			       STYLE_COLUMN, PANGO_STYLE_OBLIQUE, -1);
1554 	} else {
1555 	    mbnode->style &= ~MBNODE_STYLE_UNREAD_CHILD;
1556 	    gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
1557 			       STYLE_COLUMN, PANGO_STYLE_NORMAL, -1);
1558 	}
1559 	g_object_unref(mbnode);
1560     }
1561 }
1562 
1563 /* bmbl_core_mailbox
1564  *
1565  * Simple function, if the mailbox is one of the five "core" mailboxes
1566  * (i.e. Inbox, Sentbox...) it returns an integer representing it's
1567  * place in the desired heirarchy in the mblist.  If the mailbox is
1568  * not a core mailbox it returns zero.
1569  * */
1570 static gint
bmbl_core_mailbox(LibBalsaMailbox * mailbox)1571 bmbl_core_mailbox(LibBalsaMailbox* mailbox)
1572 {
1573     static const int num_core_mailboxes = 5;
1574     LibBalsaMailbox* core_mailbox[] = {
1575 #if !defined(ENABLE_TOUCH_UI)
1576         balsa_app.inbox,
1577         balsa_app.sentbox,
1578         balsa_app.draftbox,
1579         balsa_app.outbox,
1580         balsa_app.trash
1581 #else /* defined(ENABLE_TOUCH_UI) */
1582         balsa_app.inbox,
1583         balsa_app.draftbox,
1584         balsa_app.outbox,
1585         balsa_app.sentbox,
1586         balsa_app.trash
1587 #endif /* defined(ENABLE_TOUCH_UI) */
1588     };
1589     gint i = 0;
1590 
1591     for (i = 0; i < num_core_mailboxes; ++i) {
1592         if (mailbox == core_mailbox[i]) {
1593             /* we want to return as if from a base-1 array */
1594             return num_core_mailboxes - i + 1;
1595         }
1596     }
1597 
1598     /* if we couldn't find the mailbox, return 0 */
1599     return 0;
1600 }
1601 
1602 gboolean
balsa_mblist_focus_mailbox(BalsaMBList * mblist,LibBalsaMailbox * mailbox)1603 balsa_mblist_focus_mailbox(BalsaMBList * mblist, LibBalsaMailbox * mailbox)
1604 {
1605     GtkTreeModel *model;
1606     GtkTreeIter iter;
1607     GtkTreeSelection *selection;
1608 
1609     g_return_val_if_fail(mblist, FALSE);
1610 
1611     model = gtk_tree_view_get_model(GTK_TREE_VIEW(mblist));
1612     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mblist));
1613 
1614     if (mailbox && balsa_find_iter_by_data(&iter, mailbox)) {
1615         if (!gtk_tree_selection_iter_is_selected(selection, &iter)) {
1616             /* moving selection to the respective mailbox.
1617                this is neccessary when the previous mailbox was closed and
1618                redundant if the mailboxes were switched (notebook_switch_page)
1619                or the mailbox is checked for the new mail arrival
1620              */
1621             GtkTreePath *path;
1622 
1623             path = gtk_tree_model_get_path(model, &iter);
1624             bmbl_expand_to_row(mblist, path);
1625             gtk_tree_selection_select_path(selection, path);
1626             gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(mblist), path, NULL,
1627                                          FALSE, 0, 0);
1628             gtk_tree_path_free(path);
1629         }
1630         return TRUE;
1631     } else {
1632         gtk_tree_selection_unselect_all(selection);
1633         return FALSE;
1634     }
1635 }
1636 
1637 /* mblist_remove_mailbox_node:
1638    remove give mailbox node from the mailbox tree.
1639    Return TRUE (or equivalent) on success, FALSE on failure.
1640 */
1641 
1642 gboolean
balsa_mblist_mailbox_node_remove(BalsaMailboxNode * mbnode)1643 balsa_mblist_mailbox_node_remove(BalsaMailboxNode* mbnode)
1644 {
1645     GtkTreeIter iter;
1646 
1647     g_return_val_if_fail(mbnode, FALSE);
1648 
1649     if (balsa_find_iter_by_data(&iter, mbnode)) {
1650 	bmbl_real_disconnect_mbnode_signals(mbnode,
1651 					    GTK_TREE_MODEL
1652 					    (balsa_app.mblist_tree_store));
1653         gtk_tree_store_remove(balsa_app.mblist_tree_store, &iter);
1654 
1655         return TRUE;
1656     }
1657 
1658     return FALSE;
1659 }
1660 
1661 /* balsa_mblist_mru_menu:
1662  * a menu showing a list of recently used mailboxes, with an option for
1663  * selecting one from the whole mailbox tree
1664  */
1665 
1666 /* The info that needs to be passed around */
1667 struct _BalsaMBListMRUEntry {
1668     GtkWindow *window;
1669     GList **url_list;
1670     GCallback user_func;
1671     gpointer user_data;
1672     gchar *url;
1673     GCallback setup_cb;
1674 };
1675 typedef struct _BalsaMBListMRUEntry BalsaMBListMRUEntry;
1676 
1677 /* The callback that's passed in must fit this prototype, although it's
1678  * cast as a GCallback */
1679 typedef void (*MRUCallback) (gchar * url, gpointer user_data);
1680 /* Callback used internally for letting the option menu know that the
1681  * option menu needs to be set up */
1682 typedef void (*MRUSetup) (gpointer user_data);
1683 
1684 /* Forward references */
1685 static GtkWidget *bmbl_mru_menu(GtkWindow * window, GList ** url_list,
1686                                 GCallback user_func, gpointer user_data,
1687                                 gboolean allow_empty, GCallback setup_cb);
1688 static BalsaMBListMRUEntry *bmbl_mru_new(GList ** url_list,
1689                                          GCallback user_func,
1690                                          gpointer user_data,
1691                                          gchar * url);
1692 static void bmbl_mru_free(BalsaMBListMRUEntry * mru);
1693 static void bmbl_mru_activate_cb(GtkWidget * widget, gpointer data);
1694 static void bmbl_mru_show_tree(GtkWidget * widget, gpointer data);
1695 static void bmbl_mru_selected_cb(GtkTreeSelection * selection,
1696                                  gpointer data);
1697 static void bmbl_mru_activated_cb(GtkTreeView * tree_view,
1698                                   GtkTreePath * path,
1699                                   GtkTreeViewColumn * column,
1700                                   gpointer data);
1701 
1702 /*
1703  * balsa_mblist_mru_menu:
1704  *
1705  * window:      parent window for the `Other...' dialog;
1706  * url_list:    pointer to a list of urls;
1707  * user_func:   called when an item is selected, with the url and
1708  *              the user_data as arguments;
1709  * user_data:   passed to the user_func callback.
1710  *
1711  * Returns a pointer to a GtkMenu.
1712  *
1713  * Takes a list of urls and creates a menu with an entry for each one
1714  * that resolves to a mailbox, labeled with the mailbox name, with a
1715  * last entry that pops up the whole mailbox tree. When an item is
1716  * clicked, user_func is called with the url and user_data as
1717  * arguments, and the url_list is updated.
1718  */
1719 GtkWidget *
balsa_mblist_mru_menu(GtkWindow * window,GList ** url_list,GCallback user_func,gpointer user_data)1720 balsa_mblist_mru_menu(GtkWindow * window, GList ** url_list,
1721                       GCallback user_func, gpointer user_data)
1722 {
1723     g_return_val_if_fail(url_list != NULL, NULL);
1724     return bmbl_mru_menu(window, url_list, user_func, user_data, FALSE,
1725                          NULL);
1726 }
1727 
1728 /*
1729  * bmbl_mru_menu:
1730  *
1731  * window, url_list, user_func, user_data:
1732  *              as for balsa_mblist_mru_menu;
1733  * allow_empty: if TRUE, a list with an empty url
1734  *              will be allowed into the menu;
1735  * setup_cb:    called when the tree has been displayed, to allow the
1736  *              display to be reset.
1737  *
1738  * Returns the GtkMenu.
1739  */
1740 static GtkWidget *
bmbl_mru_menu(GtkWindow * window,GList ** url_list,GCallback user_func,gpointer user_data,gboolean allow_empty,GCallback setup_cb)1741 bmbl_mru_menu(GtkWindow * window, GList ** url_list,
1742               GCallback user_func, gpointer user_data,
1743               gboolean allow_empty, GCallback setup_cb)
1744 {
1745     GtkWidget *menu = gtk_menu_new();
1746     GtkWidget *item;
1747     GList *list;
1748     BalsaMBListMRUEntry *mru;
1749 
1750     for (list = *url_list; list; list = g_list_next(list)) {
1751         gchar *url = list->data;
1752         LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);
1753 
1754         if (mailbox || (allow_empty && !*url)) {
1755             mru = bmbl_mru_new(url_list, user_func, user_data, url);
1756             item =
1757                 gtk_menu_item_new_with_label(mailbox ? mailbox->name : "");
1758             gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1759             g_signal_connect_data(item, "activate",
1760                                   G_CALLBACK(bmbl_mru_activate_cb), mru,
1761                                   (GClosureNotify) bmbl_mru_free,
1762                                   (GConnectFlags) 0);
1763         }
1764     }
1765 
1766     gtk_menu_shell_append(GTK_MENU_SHELL(menu),
1767                           gtk_separator_menu_item_new());
1768 
1769     mru = bmbl_mru_new(url_list, user_func, user_data, NULL);
1770     mru->window = window;
1771     mru->setup_cb = setup_cb;
1772     item = gtk_menu_item_new_with_mnemonic(_("_Other..."));
1773     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1774     g_signal_connect_data(item, "activate",
1775                           G_CALLBACK(bmbl_mru_show_tree), mru,
1776                           (GClosureNotify) g_free, (GConnectFlags) 0);
1777 
1778     gtk_widget_show_all(menu);
1779 
1780     return menu;
1781 }
1782 
1783 /*
1784  * bmbl_mru_new:
1785  *
1786  * url_list, user_func, user_data:
1787  *              as for balsa_mblist_mru_menu;
1788  * url:         url of a mailbox.
1789  *
1790  * Returns a newly allocated BalsaMBListMRUEntry structure, initialized
1791  * with the data.
1792  */
1793 static BalsaMBListMRUEntry *
bmbl_mru_new(GList ** url_list,GCallback user_func,gpointer user_data,gchar * url)1794 bmbl_mru_new(GList ** url_list, GCallback user_func, gpointer user_data,
1795              gchar * url)
1796 {
1797     BalsaMBListMRUEntry *mru = g_new(BalsaMBListMRUEntry, 1);
1798 
1799     mru->url_list = url_list;
1800     mru->user_func = user_func;
1801     mru->user_data = user_data;
1802     mru->url = g_strdup(url);
1803 
1804     return mru;
1805 }
1806 
1807 static void
bmbl_mru_free(BalsaMBListMRUEntry * mru)1808 bmbl_mru_free(BalsaMBListMRUEntry * mru)
1809 {
1810     g_free(mru->url);
1811     g_free(mru);
1812 }
1813 
1814 /*
1815  * bmbl_mru_activate_cb:
1816  *
1817  * Callback for the "activate" signal of a menu item.
1818  */
1819 static void
bmbl_mru_activate_cb(GtkWidget * item,gpointer data)1820 bmbl_mru_activate_cb(GtkWidget * item, gpointer data)
1821 {
1822     BalsaMBListMRUEntry *mru = (BalsaMBListMRUEntry *) data;
1823 
1824     balsa_mblist_mru_add(mru->url_list, mru->url);
1825     if (mru->user_func)
1826         ((MRUCallback) mru->user_func) (mru->url, mru->user_data);
1827 }
1828 
1829 /*
1830  * bmbl_mru_show_tree:
1831  *
1832  * Callback for the "activate" signal of the last menu item.
1833  * Pops up a GtkDialog with a BalsaMBList.
1834  */
1835 
1836 /*
1837  * bmbl_mru_size_allocate_cb:
1838  *
1839  * Callback for the dialog's "size-allocate" signal.
1840  * Remember the width and height.
1841  */
1842 static void
bmbl_mru_size_allocate_cb(GtkWidget * widget,GdkRectangle * allocation,gpointer user_data)1843 bmbl_mru_size_allocate_cb(GtkWidget * widget, GdkRectangle * allocation,
1844                           gpointer user_data)
1845 {
1846     GdkWindow *gdk_window;
1847 
1848     /* Maximizing a GtkDialog may not be possible, but we check anyway. */
1849     if ((gdk_window = gtk_widget_get_window(widget))
1850         && !(gdk_window_get_state(gdk_window)
1851              & GDK_WINDOW_STATE_MAXIMIZED)) {
1852         balsa_app.mru_tree_width  = allocation->width;
1853         balsa_app.mru_tree_height = allocation->height;
1854     }
1855 }
1856 
1857 static void
bmbl_mru_show_tree(GtkWidget * widget,gpointer data)1858 bmbl_mru_show_tree(GtkWidget * widget, gpointer data)
1859 {
1860     BalsaMBListMRUEntry *mru = data;
1861     GtkWidget *dialog;
1862     GtkWidget *scroll;
1863     GtkWidget *mblist;
1864     GtkTreeSelection *selection;
1865 
1866     mblist = balsa_mblist_new();
1867     g_signal_connect(mblist, "row-activated",
1868                      G_CALLBACK(bmbl_mru_activated_cb), data);
1869 
1870     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mblist));
1871     g_signal_connect(selection, "changed",
1872                      G_CALLBACK(bmbl_mru_selected_cb), data);
1873 
1874     scroll = gtk_scrolled_window_new(NULL, NULL);
1875     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1876                                    GTK_POLICY_AUTOMATIC,
1877                                    GTK_POLICY_AUTOMATIC);
1878     gtk_container_add(GTK_CONTAINER(scroll), mblist);
1879     gtk_widget_show_all(scroll);
1880 
1881     dialog =
1882         gtk_dialog_new_with_buttons(_("Choose destination folder"),
1883                                     mru->window,
1884                                     GTK_DIALOG_MODAL,
1885                                     GTK_STOCK_CANCEL,
1886                                     GTK_RESPONSE_CANCEL,
1887                                     NULL);
1888 #if HAVE_MACOSX_DESKTOP
1889     libbalsa_macosx_menu_for_parent(dialog, mru->window);
1890 #endif
1891     gtk_box_pack_start(GTK_BOX
1892                       (gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
1893                       scroll, TRUE, TRUE, 0);
1894 
1895     g_signal_connect(dialog, "size-allocate",
1896                      G_CALLBACK(bmbl_mru_size_allocate_cb), NULL);
1897     gtk_window_set_default_size(GTK_WINDOW(dialog),
1898                                 balsa_app.mru_tree_width,
1899                                 balsa_app.mru_tree_height);
1900 
1901     gtk_dialog_run(GTK_DIALOG(dialog));
1902     gtk_widget_destroy(dialog);
1903     if (mru->setup_cb)
1904         ((MRUSetup) mru->setup_cb) (mru->user_data);
1905 }
1906 
1907 /*
1908  * bmbl_mru_selected_cb:
1909  *
1910  * Callback for the "changed" signal of the GtkTreeSelection in the
1911  * BalsaMBList object. This permits one-click selection of a mailbox.
1912  *
1913  * Emulates selecting one of the other menu items, and closes the dialog.
1914  */
1915 static void
bmbl_mru_selected_cb(GtkTreeSelection * selection,gpointer data)1916 bmbl_mru_selected_cb(GtkTreeSelection * selection, gpointer data)
1917 {
1918     GdkEvent *event;
1919     GtkTreeView *tree_view;
1920     GtkTreePath *path;
1921 
1922     if (!data)
1923         return;
1924 
1925     event = gtk_get_current_event();
1926     if (!event)
1927         return;
1928 
1929     tree_view = gtk_tree_selection_get_tree_view(selection);
1930     if (event->type != GDK_BUTTON_PRESS ||
1931         !gtk_tree_view_get_path_at_pos(tree_view, event->button.x,
1932                                        event->button.y, &path,
1933                                        NULL, NULL, NULL)) {
1934         gtk_tree_selection_unselect_all(selection);
1935         gdk_event_free(event);
1936         return;
1937     }
1938 
1939     if (gtk_tree_selection_path_is_selected(selection, path)) {
1940         GtkTreeModel *model;
1941         GtkTreeIter iter;
1942         BalsaMailboxNode *mbnode;
1943 
1944         model = gtk_tree_view_get_model(tree_view);
1945         gtk_tree_model_get_iter(model, &iter, path);
1946         gtk_tree_model_get(model, &iter, 0, &mbnode, -1);
1947         ((BalsaMBListMRUEntry *) data)->url = g_strdup(mbnode->mailbox->url);
1948 	g_object_unref(mbnode);
1949         bmbl_mru_activate_cb(NULL, data);
1950 
1951         gtk_dialog_response(GTK_DIALOG
1952                             (gtk_widget_get_ancestor
1953                              (GTK_WIDGET(tree_view), GTK_TYPE_DIALOG)),
1954                             GTK_RESPONSE_OK);
1955     }
1956 
1957     gtk_tree_path_free(path);
1958     gdk_event_free(event);
1959 }
1960 
1961 /*
1962  * bmbl_mru_activated_cb:
1963  *
1964  * Callback for the "row-activated" signal of the GtkTreeView in the
1965  * BalsaMBList object. This permits keyboard selection of a mailbox.
1966  */
1967 static void
bmbl_mru_activated_cb(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer data)1968 bmbl_mru_activated_cb(GtkTreeView * tree_view, GtkTreePath * path,
1969                       GtkTreeViewColumn * column, gpointer data)
1970 {
1971     GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
1972     GtkTreeIter iter;
1973     BalsaMailboxNode *mbnode;
1974 
1975     gtk_tree_model_get_iter(model, &iter, path);
1976     gtk_tree_model_get(model, &iter, MBNODE_COLUMN, &mbnode, -1);
1977     g_return_if_fail(mbnode != NULL);
1978 
1979     if (mbnode->mailbox) {
1980         ((BalsaMBListMRUEntry *) data)->url =
1981             g_strdup(mbnode->mailbox->url);
1982         bmbl_mru_activate_cb(NULL, data);
1983 
1984         gtk_dialog_response(GTK_DIALOG
1985                             (gtk_widget_get_ancestor
1986                              (GTK_WIDGET(tree_view), GTK_TYPE_DIALOG)),
1987                             GTK_RESPONSE_OK);
1988     }
1989     g_object_unref(mbnode);
1990 }
1991 
1992 /* balsa_mblist_mru_add/drop:
1993    Add given folder's url to mailbox-recently-used list.
1994    Drop it first, so that if it's already there, it moves to the top.
1995 */
1996 #define FOLDER_MRU_LENGTH 10
1997 void
balsa_mblist_mru_add(GList ** list,const gchar * url)1998 balsa_mblist_mru_add(GList ** list, const gchar * url)
1999 {
2000     balsa_mblist_mru_drop(list, url);
2001     while (g_list_length(*list) >= FOLDER_MRU_LENGTH) {
2002         GList *tmp = g_list_last(*list);
2003 
2004         g_free(tmp->data);
2005         *list = g_list_delete_link(*list, tmp);
2006     }
2007     *list = g_list_prepend(*list, g_strdup(url));
2008 
2009     if (list != &balsa_app.folder_mru) {
2010         /* Update the folder MRU list as well */
2011         balsa_mblist_mru_add(&balsa_app.folder_mru, url);
2012     }
2013 }
2014 
2015 void
balsa_mblist_mru_drop(GList ** list,const gchar * url)2016 balsa_mblist_mru_drop(GList ** list, const gchar * url)
2017 {
2018     GList *tmp;
2019 
2020     for (tmp = *list; tmp; tmp = g_list_next(tmp)) {
2021         if (!strcmp((char *) tmp->data, url)) {
2022             g_free(tmp->data);
2023             *list = g_list_delete_link(*list, tmp);
2024             break;
2025         }
2026     }
2027 }
2028 
2029 /* balsa_mblist_mru_option_menu: create a GtkComboBox to manage an MRU
2030  * list */
2031 
2032 /* The info that needs to be passed around */
2033 struct _BalsaMBListMRUOption {
2034     GtkWindow *window;
2035     GList **url_list;
2036     GSList *real_urls;
2037 };
2038 typedef struct _BalsaMBListMRUOption BalsaMBListMRUOption;
2039 
2040 /* Helper. */
2041 static void bmbl_mru_combo_box_changed(GtkComboBox * combo_box,
2042                                        BalsaMBListMRUOption * mro);
2043 static void
bmbl_mru_combo_box_setup(GtkComboBox * combo_box)2044 bmbl_mru_combo_box_setup(GtkComboBox * combo_box)
2045 {
2046     BalsaMBListMRUOption *mro =
2047         g_object_get_data(G_OBJECT(combo_box), "mro");
2048     GList *list;
2049     GtkListStore *store;
2050     GtkTreeIter iter;
2051 
2052     gtk_combo_box_set_active(combo_box, -1);
2053     store = GTK_LIST_STORE(gtk_combo_box_get_model(combo_box));
2054     gtk_list_store_clear(store);
2055     g_slist_foreach(mro->real_urls, (GFunc) g_free, NULL);
2056     g_slist_free(mro->real_urls);
2057     mro->real_urls = NULL;
2058 
2059     for (list = *mro->url_list; list; list = list->next) {
2060         const gchar *url = list->data;
2061 
2062         gchar * short_name = balsa_get_short_mailbox_name(url);
2063         gtk_list_store_append(store, &iter);
2064         gtk_list_store_set(store, &iter,
2065                            0, short_name,
2066                            1, FALSE, -1);
2067         g_free(short_name);
2068         mro->real_urls = g_slist_append(mro->real_urls, g_strdup(url));
2069     }
2070 
2071     /* Separator: */
2072     gtk_list_store_append(store, &iter);
2073     gtk_list_store_set(store, &iter, 1, TRUE, -1);
2074     gtk_list_store_append(store, &iter);
2075     gtk_list_store_set(store, &iter,
2076                        0, _("Other..."),
2077 		       1, FALSE, -1);
2078     gtk_combo_box_set_active(combo_box, 0);
2079 }
2080 
2081 /* Callbacks */
2082 static void
bmbl_mru_combo_box_changed(GtkComboBox * combo_box,BalsaMBListMRUOption * mro)2083 bmbl_mru_combo_box_changed(GtkComboBox * combo_box,
2084                            BalsaMBListMRUOption * mro)
2085 {
2086     BalsaMBListMRUEntry mru;
2087     const gchar *url;
2088     gint active = gtk_combo_box_get_active(combo_box);
2089 
2090     if (active < 0)
2091 	return;
2092     if ((url = g_slist_nth_data(mro->real_urls, active))) {
2093 	/* Move this url to the top. */
2094 	balsa_mblist_mru_add(mro->url_list, url);
2095         return;
2096     }
2097 
2098     /* User clicked on "Other..." */
2099     mru.window = mro->window;
2100     mru.url_list = mro->url_list;
2101     mru.user_func = NULL;
2102     mru.setup_cb = NULL;
2103     mru.user_data = combo_box;
2104     mru.url = NULL;
2105     bmbl_mru_show_tree(NULL, &mru);
2106     g_free(mru.url);
2107     bmbl_mru_combo_box_setup(combo_box);
2108 }
2109 
2110 static void
bmbl_mru_combo_box_destroy_cb(BalsaMBListMRUOption * mro)2111 bmbl_mru_combo_box_destroy_cb(BalsaMBListMRUOption * mro)
2112 {
2113     g_slist_foreach(mro->real_urls, (GFunc) g_free, NULL);
2114     g_slist_free(mro->real_urls);
2115     g_free(mro);
2116 }
2117 
2118 /*
2119  * balsa_mblist_mru_option_menu:
2120  *
2121  * window:      parent window for the dialog;
2122  * url_list:    pointer to a list of urls;
2123  *
2124  * Returns a GtkComboBox.
2125  *
2126  * Takes a list of urls and creates a combo-box with an entry for
2127  * each one that resolves to a mailbox, labeled with the mailbox name,
2128  * including the special case of an empty url and a NULL mailbox.
2129  * Adds a last entry that pops up the whole mailbox tree. When an item
2130  * is clicked, the url_list is updated.
2131  */
2132 static gboolean
bmbl_mru_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)2133 bmbl_mru_separator_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
2134 {
2135     gboolean is_sep;
2136 
2137     gtk_tree_model_get(model, iter, 1, &is_sep, -1);
2138 
2139     return is_sep;
2140 }
2141 
2142 GtkWidget *
balsa_mblist_mru_option_menu(GtkWindow * window,GList ** url_list)2143 balsa_mblist_mru_option_menu(GtkWindow * window, GList ** url_list)
2144 {
2145     GtkWidget *combo_box;
2146     BalsaMBListMRUOption *mro;
2147     GtkListStore *store;
2148     GtkCellRenderer *renderer;
2149 
2150     g_return_val_if_fail(url_list != NULL, NULL);
2151 
2152     store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_BOOLEAN);
2153     combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
2154     renderer = gtk_cell_renderer_text_new();
2155     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE);
2156     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), renderer,
2157                                    "text", 0, NULL);
2158     gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(combo_box),
2159                                          bmbl_mru_separator_func, NULL,
2160                                          NULL);
2161     mro = g_new(BalsaMBListMRUOption, 1);
2162 
2163     mro->window = window;
2164     mro->url_list = url_list;
2165     mro->real_urls = NULL;
2166 
2167     g_object_set_data_full(G_OBJECT(combo_box), "mro", mro,
2168                            (GDestroyNotify) bmbl_mru_combo_box_destroy_cb);
2169     bmbl_mru_combo_box_setup(GTK_COMBO_BOX(combo_box));
2170     g_signal_connect(G_OBJECT(combo_box), "changed",
2171                      G_CALLBACK(bmbl_mru_combo_box_changed), mro);
2172 
2173     return combo_box;
2174 }
2175 
2176 /*
2177  * balsa_mblist_mru_option_menu_set
2178  *
2179  * combo_box: a GtkComboBox created by balsa_mblist_mru_option_menu;
2180  * url:       URL of a mailbox
2181  *
2182  * Adds url to the front of the url_list managed by combo_box, resets
2183  * combo_box to show the new url, and stores a copy in the mro
2184  * structure.
2185  */
2186 void
balsa_mblist_mru_option_menu_set(GtkWidget * combo_box,const gchar * url)2187 balsa_mblist_mru_option_menu_set(GtkWidget * combo_box, const gchar * url)
2188 {
2189     BalsaMBListMRUOption *mro =
2190         g_object_get_data(G_OBJECT(combo_box), "mro");
2191 
2192     balsa_mblist_mru_add(mro->url_list, url);
2193     bmbl_mru_combo_box_setup(GTK_COMBO_BOX(combo_box));
2194 }
2195 
2196 /*
2197  * balsa_mblist_mru_option_menu_get
2198  *
2199  * combo_box: a GtkComboBox created by balsa_mblist_mru_option_menu.
2200  *
2201  * Returns the address of the current URL.
2202  *
2203  * Note that the url is held in the mro structure, and is freed when the
2204  * widget is destroyed. The calling code must make its own copy of the
2205  * string if it is needed more than temporarily.
2206  */
2207 const gchar *
balsa_mblist_mru_option_menu_get(GtkWidget * combo_box)2208 balsa_mblist_mru_option_menu_get(GtkWidget * combo_box)
2209 {
2210     gint active;
2211     BalsaMBListMRUOption *mro =
2212         g_object_get_data(G_OBJECT(combo_box), "mro");
2213 
2214     active = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_box));
2215 
2216     return g_slist_nth_data(mro->real_urls, active);
2217 }
2218 
2219 static void
bmbl_expand_to_row(BalsaMBList * mblist,GtkTreePath * path)2220 bmbl_expand_to_row(BalsaMBList * mblist, GtkTreePath * path)
2221 {
2222     GtkTreePath *tmp = gtk_tree_path_copy(path);
2223 
2224     if (gtk_tree_path_up(tmp) && gtk_tree_path_get_depth(tmp) > 0
2225         && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(mblist), tmp)) {
2226 	gtk_tree_view_expand_to_path(GTK_TREE_VIEW(mblist), tmp);
2227     }
2228 
2229     gtk_tree_path_free(tmp);
2230 }
2231 
2232 /* Make a new row for mbnode in balsa_app.mblist_tree_store; the row
2233  * will be a child to the row for root, if we find it, and a top-level
2234  * row otherwise. */
2235 static gboolean
bmbl_sort_idle(gpointer data)2236 bmbl_sort_idle(gpointer data)
2237 {
2238     GtkTreeSortable *sortable = data;
2239 
2240     gdk_threads_enter();
2241 
2242     gtk_tree_sortable_set_sort_column_id(sortable,
2243                                          balsa_app.mblist->sort_column_id,
2244                                          GTK_SORT_ASCENDING);
2245     balsa_app.mblist->sort_idle_id = 0;
2246     g_object_unref(sortable);
2247 
2248     gdk_threads_leave();
2249 
2250     return FALSE;
2251 }
2252 
2253 void
balsa_mblist_mailbox_node_append(BalsaMailboxNode * root,BalsaMailboxNode * mbnode)2254 balsa_mblist_mailbox_node_append(BalsaMailboxNode * root,
2255 				 BalsaMailboxNode * mbnode)
2256 {
2257     GtkTreeModel *model;
2258     GtkTreeIter parent;
2259     GtkTreeIter *parent_iter = NULL;
2260     GtkTreeIter iter;
2261 
2262     gdk_threads_enter();
2263 
2264     model = GTK_TREE_MODEL(balsa_app.mblist_tree_store);
2265 
2266     if (!balsa_app.mblist->sort_idle_id) {
2267         GtkTreeSortable *sortable = GTK_TREE_SORTABLE(model);
2268         gtk_tree_sortable_get_sort_column_id
2269             (sortable, &balsa_app.mblist->sort_column_id, NULL);
2270         gtk_tree_sortable_set_sort_column_id
2271             (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
2272                        GTK_SORT_ASCENDING);
2273         balsa_app.mblist->sort_idle_id =
2274             g_idle_add(bmbl_sort_idle, g_object_ref(sortable));
2275     }
2276 
2277     if (root && balsa_find_iter_by_data(&parent, root))
2278 	parent_iter = &parent;
2279     gtk_tree_store_prepend(balsa_app.mblist_tree_store, &iter, parent_iter);
2280     bmbl_store_redraw_mbnode(&iter, mbnode);
2281 
2282     if (parent_iter) {
2283         /* Check whether this node is exposed. */
2284         GtkTreePath *parent_path =
2285             gtk_tree_model_get_path(model, parent_iter);
2286         if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(balsa_app.mblist),
2287                                        parent_path)
2288             && !mbnode->scanned) {
2289             /* Check this node for children. */
2290             balsa_mailbox_node_append_subtree(mbnode);
2291             mbnode->scanned = TRUE;
2292         }
2293         gtk_tree_path_free(parent_path);
2294     }
2295 
2296     /* The tree-store owns mbnode. */
2297     g_object_unref(mbnode);
2298 
2299     gdk_threads_leave();
2300 }
2301 
2302 /* Rerender a row after its properties have changed. */
2303 void
balsa_mblist_mailbox_node_redraw(BalsaMailboxNode * mbnode)2304 balsa_mblist_mailbox_node_redraw(BalsaMailboxNode * mbnode)
2305 {
2306     GtkTreeIter iter;
2307     if (balsa_find_iter_by_data(&iter, mbnode))
2308 	bmbl_store_redraw_mbnode(&iter, mbnode);
2309     balsa_window_update_tab(mbnode);
2310 }
2311