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