1 /* Notification plugin for Claws-Mail
2 * Copyright (C) 2005-2007 Holger Berndt
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 /* This code is based on foldersel.c in Claws Mail.
19 * Some functions are only slightly modified, almost 1:1 copies from there. */
23 # include "claws-features.h"
27 #include <glib/gi18n.h>
29 /* Basic definitions first */
30 #include "common/defs.h"
34 #include <gdk/gdkkeysyms.h>
37 /* Claws Mail includes */
38 #include "manage_window.h"
40 #include "stock_pixmap.h"
41 #include "gtk/gtkutils.h"
42 #include "common/utils.h"
43 #include "common/prefs.h"
44 #include "common/xml.h"
45 #include "common/hooks.h"
46 #include "prefs_common.h"
49 #include "notification_foldercheck.h"
51 /* enums and structures */
53 FOLDERCHECK_FOLDERNAME,
54 FOLDERCHECK_FOLDERITEM,
56 FOLDERCHECK_PIXBUF_OPEN,
65 GtkTreeStore *tree_store;
72 } SpecificFolderArrayEntry;
74 /* variables with file scope */
75 static GdkPixbuf *folder_pixbuf;
76 static GdkPixbuf *folderopen_pixbuf;
77 static GdkPixbuf *foldernoselect_pixbuf;
79 static GArray *specific_folder_array;
80 static guint specific_folder_array_size;
82 static guint hook_folder_update;
86 #define FOLDERCHECK_ARRAY "notification_foldercheck.xml"
87 #define foldercheck_get_entry_from_id(id) \
88 ((id) < specific_folder_array_size) ? \
89 g_array_index(specific_folder_array,SpecificFolderArrayEntry*,(id)) : NULL
91 /* function prototypes */
92 static void folder_checked(guint);
93 static void foldercheck_create_window(SpecificFolderArrayEntry*);
94 static void foldercheck_destroy_window(SpecificFolderArrayEntry*);
95 static gint foldercheck_folder_name_compare(GtkTreeModel*, GtkTreeIter*,
96 GtkTreeIter*, gpointer);
97 static gboolean foldercheck_selected(GtkTreeSelection*,
98 GtkTreeModel*, GtkTreePath*,
101 static gint delete_event(GtkWidget*, GdkEventAny*, gpointer);
102 static void foldercheck_ok(GtkButton*, gpointer);
103 static void foldercheck_cancel(GtkButton*, gpointer);
104 static void foldercheck_set_tree(SpecificFolderArrayEntry*);
105 static void foldercheck_insert_gnode_in_store(GtkTreeStore*, GNode*,
107 static void foldercheck_append_item(GtkTreeStore*, FolderItem*,
108 GtkTreeIter*, GtkTreeIter*);
109 static void foldercheck_recursive_cb(GtkToggleButton*, gpointer);
110 static void folder_toggle_cb(GtkCellRendererToggle*, gchar*, gpointer);
111 static void folder_toggle_recurse_tree(GtkTreeStore*, GtkTreeIter*, gint,
113 static gboolean foldercheck_foreach_check(GtkTreeModel*, GtkTreePath*,
114 GtkTreeIter*, gpointer);
115 static gboolean foldercheck_foreach_update_to_list(GtkTreeModel*, GtkTreePath*,
116 GtkTreeIter*, gpointer);
117 static gchar *foldercheck_get_array_path(void);
118 static gboolean my_folder_update_hook(gpointer, gpointer);
119 static gboolean key_pressed(GtkWidget*, GdkEventKey*,gpointer);
122 /* Creates an entry in the specific_folder_array, and fills it with a new
123 * SpecificFolderArrayEntry*. If specific_folder_array already has an entry
124 * with the same name, return its ID. (The ID is the index in the array.) */
125 guint notification_register_folder_specific_list(gchar *node_name)
127 SpecificFolderArrayEntry *entry;
130 /* If array does not yet exist, create it. */
131 if(!specific_folder_array) {
132 specific_folder_array = g_array_new(FALSE, FALSE,
133 sizeof(SpecificFolderArrayEntry*));
134 specific_folder_array_size = 0;
136 /* Register hook for folder update */
137 /* "The hook is registered" is bound to "the array is allocated" */
138 hook_folder_update = hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
139 my_folder_update_hook, NULL);
140 if(hook_folder_update == (guint) -1) {
141 debug_print("Warning: Failed to register hook to folder update "
143 "Strange things can occur when deleting folders.\n");
147 /* Check if we already have such a name. If so, return its id. */
148 while(ii < specific_folder_array_size) {
149 entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
151 if(!strcmp2(entry->name,node_name))
157 /* Create an entry with the corresponding node name. */
158 entry = g_new(SpecificFolderArrayEntry, 1);
159 entry->name = g_strdup(node_name);
161 entry->window = NULL;
162 entry->treeview = NULL;
163 entry->cancelled = FALSE;
164 entry->finished = FALSE;
165 entry->recursive = FALSE;
166 entry->tree_store = gtk_tree_store_new(N_FOLDERCHECK_COLUMNS,
172 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(entry->tree_store),
173 FOLDERCHECK_FOLDERNAME,
174 foldercheck_folder_name_compare,
176 specific_folder_array = g_array_append_val(specific_folder_array, entry);
177 return specific_folder_array_size++;
180 /* This function is called in plugin_done. It frees the whole
181 * folder_specific_array with all its entries. */
182 void notification_free_folder_specific_array(void)
185 SpecificFolderArrayEntry *entry;
187 for(ii = 0; ii < specific_folder_array_size; ii++) {
188 entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
192 g_slist_free(entry->list);
193 if(entry->tree_store)
194 g_object_unref(G_OBJECT(entry->tree_store));
198 if(specific_folder_array) {
200 g_array_free(specific_folder_array, TRUE);
202 /* Unregister hook */
203 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_folder_update);
205 specific_folder_array = NULL;
206 specific_folder_array_size = 0;
209 /* Returns the list of the entry with the corresponding ID, or NULL if
210 * no such element exists. */
211 GSList* notification_foldercheck_get_list(guint id)
213 SpecificFolderArrayEntry *entry;
215 entry = foldercheck_get_entry_from_id(id);
223 /* Save selections in a common xml-file. Called when unloading the plugin.
224 * This is analog to folder.h::folder_write_list. */
225 void notification_foldercheck_write_array(void)
234 /* Do nothing if foldercheck is not in use */
235 if(specific_folder_array_size == 0)
238 path = foldercheck_get_array_path();
239 if((pfile = prefs_write_open(path)) == NULL) {
240 debug_print("Notification Plugin Error: Cannot open "
241 "file " FOLDERCHECK_ARRAY " for writing\n");
245 /* XML declarations */
246 xml_file_put_xml_decl(pfile->fp);
248 /* Build up XML tree */
251 tag = xml_tag_new("foldercheckarray");
252 xmlnode = xml_node_new(tag, NULL);
253 rootnode = g_node_new(xmlnode);
256 for(ii = 0; ii < specific_folder_array_size; ii++) {
259 SpecificFolderArrayEntry *entry;
261 entry = foldercheck_get_entry_from_id(ii);
263 tag = xml_tag_new("branch");
264 xml_tag_add_attr(tag, xml_attr_new("name",entry->name));
265 xmlnode = xml_node_new(tag, NULL);
266 branchnode = g_node_new(xmlnode);
267 g_node_append(rootnode, branchnode);
269 /* Write out the list as leaf nodes */
270 for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
273 FolderItem *item = (FolderItem*) walk->data;
275 identifier = folder_item_get_identifier(item);
277 tag = xml_tag_new("folderitem");
278 xml_tag_add_attr(tag, xml_attr_new("identifier", identifier));
280 xmlnode = xml_node_new(tag, NULL);
281 node = g_node_new(xmlnode);
282 g_node_append(branchnode, node);
283 } /* for all list elements in branch node */
285 } /* for all branch nodes */
287 /* Actual writing and cleanup */
288 xml_write_tree(rootnode, pfile->fp);
290 if(prefs_file_close(pfile) < 0) {
291 debug_print("Notification Plugin Error: Failed to write "
292 "file " FOLDERCHECK_ARRAY "\n");
296 xml_free_tree(rootnode);
299 /* Read selections from a common xml-file. Called when loading the plugin.
300 * Returns TRUE if data has been read, FALSE if no data is available
301 * or an error occured.
302 * This is analog to folder.h::folder_read_list. */
303 gboolean notification_foldercheck_read_array(void)
306 GNode *rootnode, *node, *branchnode;
308 gboolean success = FALSE;
310 path = foldercheck_get_array_path();
311 if(!is_file_exist(path)) {
316 /* We don't do merging, so if the file existed, clear what we
317 have stored in memory right now.. */
318 notification_free_folder_specific_array();
320 /* .. and evaluate the file */
321 rootnode = xml_parse_file(path);
326 xmlnode = rootnode->data;
328 /* Check that root entry is "foldercheckarray" */
329 if(strcmp2(xmlnode->tag->tag, "foldercheckarray") != 0) {
330 g_warning("wrong foldercheck array file\n");
331 xml_free_tree(rootnode);
335 /* Process branch entries */
336 for(branchnode = rootnode->children; branchnode != NULL;
337 branchnode = branchnode->next) {
340 SpecificFolderArrayEntry *entry = NULL;
342 xmlnode = branchnode->data;
343 if(strcmp2(xmlnode->tag->tag, "branch") != 0) {
344 g_warning("tag name != \"branch\"\n");
348 /* Attributes of the branch nodes */
349 list = xmlnode->tag->attr;
350 for(; list != NULL; list = list->next) {
351 XMLAttr *attr = list->data;
353 if(attr && attr->name && attr->value && !strcmp2(attr->name, "name")) {
354 id = notification_register_folder_specific_list(attr->value);
355 entry = foldercheck_get_entry_from_id(id);
356 /* We have found something */
361 if((list == NULL) || (entry == NULL)) {
362 g_warning("Did not find attribute \"name\" in tag \"branch\"\n");
363 continue; /* with next branch */
366 /* Now descent into the children of the brach, which are the folderitems */
367 for(node = branchnode->children; node != NULL; node = node->next) {
368 FolderItem *item = NULL;
370 /* These should all be leaves. */
371 if(!G_NODE_IS_LEAF(node))
372 g_warning("Subnodes in \"branch\" nodes should all be leaves. "
373 "Ignoring deeper subnodes..\n");
375 /* Check if tag is "folderitem" */
376 xmlnode = node->data;
377 if(strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
378 g_warning("tag name != \"folderitem\"\n");
379 continue; /* to next node in branch */
382 /* Attributes of the leaf nodes */
383 list = xmlnode->tag->attr;
384 for(; list != NULL; list = list->next) {
385 XMLAttr *attr = list->data;
387 if(attr && attr->name && attr->value &&
388 !strcmp2(attr->name, "identifier")) {
389 item = folder_find_item_from_identifier(attr->value);
393 if((list == NULL) || (item == NULL)) {
394 g_warning("Did not find attribute \"identifier\" in tag "
396 continue; /* with next leaf node */
399 /* Store all FolderItems in the list */
400 /* We started with a cleared array, so we don't need to check if
401 it's already in there. */
402 entry->list = g_slist_prepend(entry->list, item);
404 } /* for all subnodes in branch */
406 } /* for all branches */
410 /* Stolen from folder.c. Return value should NOT be freed. */
411 static gchar *foldercheck_get_array_path(void)
413 static gchar *filename = NULL;
416 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
417 FOLDERCHECK_ARRAY, NULL);
421 /* Callback for selecting folders. If no selection dialog exists yet, create
422 * one and initialize the selection. The recurse through the whole model, and
423 * add all selected items to the list. */
424 static void folder_checked(guint id)
426 SpecificFolderArrayEntry *entry;
427 GSList *checked_list = NULL;
429 entry = foldercheck_get_entry_from_id(id);
432 foldercheck_create_window(entry);
433 gtk_widget_show(entry->window);
434 manage_window_set_transient(GTK_WINDOW(entry->window));
436 entry->cancelled = entry->finished = FALSE;
437 while(entry->finished == FALSE)
438 gtk_main_iteration();
440 foldercheck_destroy_window(entry);
442 if(!entry->cancelled) {
443 /* recurse through the whole model, add all selected items to the list */
444 gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
445 foldercheck_foreach_check, &checked_list);
448 g_slist_free(entry->list);
451 entry->list = g_slist_copy(checked_list);
452 g_slist_free(checked_list);
455 gtk_tree_store_clear(entry->tree_store);
457 entry->cancelled = FALSE;
458 entry->finished = FALSE;
461 /* Create the window for selecting folders with checkboxes */
462 static void foldercheck_create_window(SpecificFolderArrayEntry *entry)
465 GtkWidget *scrolledwin;
466 GtkWidget *confirm_area;
468 GtkWidget *cancel_button;
469 GtkWidget *ok_button;
470 GtkTreeSelection *selection;
471 GtkTreeViewColumn *column;
472 GtkCellRenderer *renderer;
473 static GdkGeometry geometry;
476 entry->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_foldercheck");
477 gtk_window_set_title(GTK_WINDOW(entry->window), _("Select folder(s)"));
478 gtk_container_set_border_width(GTK_CONTAINER(entry->window), 4);
479 gtk_window_set_position(GTK_WINDOW(entry->window), GTK_WIN_POS_CENTER);
480 gtk_window_set_modal(GTK_WINDOW(entry->window), TRUE);
481 gtk_window_set_resizable(GTK_WINDOW(entry->window), TRUE);
482 gtk_window_set_wmclass
483 (GTK_WINDOW(entry->window), "folder_selection", "Claws Mail");
484 g_signal_connect(G_OBJECT(entry->window), "delete_event",
485 G_CALLBACK(delete_event), entry);
486 g_signal_connect(G_OBJECT(entry->window), "key_press_event",
487 G_CALLBACK(key_pressed), entry);
488 MANAGE_WINDOW_SIGNALS_CONNECT(entry->window);
491 vbox = gtk_vbox_new(FALSE, 4);
492 gtk_container_add(GTK_CONTAINER(entry->window), vbox);
494 /* scrolled window */
495 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
496 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
497 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
498 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
500 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
504 stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_CLOSE,
506 if(!folderopen_pixbuf)
507 stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_OPEN,
509 if(!foldernoselect_pixbuf)
510 stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_NOSELECT,
511 &foldernoselect_pixbuf);
514 foldercheck_set_tree(entry);
515 gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
516 foldercheck_foreach_update_to_list, entry);
521 gtk_tree_view_new_with_model(GTK_TREE_MODEL(entry->tree_store));
522 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(entry->treeview), FALSE);
523 gtk_tree_view_set_search_column(GTK_TREE_VIEW(entry->treeview),
524 FOLDERCHECK_FOLDERNAME);
525 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(entry->treeview),
526 prefs_common_get_prefs()->use_stripes_everywhere);
527 gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(entry->treeview), FALSE);
529 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(entry->treeview));
530 gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
531 gtk_tree_selection_set_select_function(selection, foldercheck_selected,
534 gtk_container_add(GTK_CONTAINER(scrolledwin), entry->treeview);
536 /* --- column 1 --- */
537 column = gtk_tree_view_column_new();
538 gtk_tree_view_column_set_title(column, "sel");
539 gtk_tree_view_column_set_spacing(column, 2);
542 renderer = gtk_cell_renderer_toggle_new();
543 g_object_set(renderer, "xalign", 0.0, NULL);
544 gtk_tree_view_column_pack_start(column, renderer, TRUE);
545 g_signal_connect(renderer, "toggled", G_CALLBACK(folder_toggle_cb),entry);
546 gtk_tree_view_column_set_attributes(column, renderer,
547 "active", FOLDERCHECK_CHECK,NULL);
549 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
550 gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
552 /* --- column 2 --- */
553 column = gtk_tree_view_column_new();
554 gtk_tree_view_column_set_title(column, "Folder");
555 gtk_tree_view_column_set_spacing(column, 2);
558 renderer = gtk_cell_renderer_pixbuf_new();
559 gtk_tree_view_column_pack_start(column, renderer, FALSE);
560 gtk_tree_view_column_set_attributes
562 "pixbuf", FOLDERCHECK_PIXBUF,
563 "pixbuf-expander-open", FOLDERCHECK_PIXBUF_OPEN,
564 "pixbuf-expander-closed", FOLDERCHECK_PIXBUF,
568 renderer = gtk_cell_renderer_text_new();
569 gtk_tree_view_column_pack_start(column, renderer, TRUE);
570 gtk_tree_view_column_set_attributes(column, renderer,
571 "text", FOLDERCHECK_FOLDERNAME,
574 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
575 gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
578 checkbox = gtk_check_button_new_with_label( _("select recursively"));
579 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), FALSE);
580 g_signal_connect(G_OBJECT(checkbox), "toggled",
581 G_CALLBACK(foldercheck_recursive_cb), entry);
582 gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 10);
584 gtkut_stock_button_set_create(&confirm_area,
585 &cancel_button, GTK_STOCK_CANCEL,
586 &ok_button, GTK_STOCK_OK,
588 gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
589 gtk_widget_grab_default(ok_button);
591 g_signal_connect(G_OBJECT(ok_button), "clicked",
592 G_CALLBACK(foldercheck_ok), entry);
593 g_signal_connect(G_OBJECT(cancel_button), "clicked",
594 G_CALLBACK(foldercheck_cancel), entry);
596 if(!geometry.min_height) {
597 geometry.min_width = 360;
598 geometry.min_height = 360;
601 gtk_window_set_geometry_hints(GTK_WINDOW(entry->window), NULL, &geometry,
604 gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
606 gtk_widget_show_all(vbox);
609 static void foldercheck_destroy_window(SpecificFolderArrayEntry *entry)
611 gtk_widget_destroy(entry->window);
612 entry->window = NULL;
613 entry->treeview = NULL;
614 entry->recursive = FALSE;
617 /* Handler for the delete event of the windows for selecting folders */
618 static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data)
620 foldercheck_cancel(NULL, data);
624 /* sortable_set_sort_func */
625 static gint foldercheck_folder_name_compare(GtkTreeModel *model,
626 GtkTreeIter *a, GtkTreeIter *b,
629 gchar *str_a = NULL, *str_b = NULL;
631 FolderItem *item_a = NULL, *item_b = NULL;
634 gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERITEM, &item_a, -1);
635 gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERITEM, &item_b, -1);
637 /* no sort for root folder */
638 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, a))
641 /* if both a and b are special folders, sort them according to
642 * their types (which is in-order). Note that this assumes that
643 * there are no multiple folders of a special type. */
644 if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL)
645 return item_a->stype - item_b->stype;
647 /* if b is normal folder, and a is not, b is smaller (ends up
648 * lower in the list) */
649 if (item_a->stype != F_NORMAL && item_b->stype == F_NORMAL)
650 return item_b->stype - item_a->stype;
652 /* if b is special folder, and a is not, b is larger (ends up
653 * higher in the list) */
654 if (item_a->stype == F_NORMAL && item_b->stype != F_NORMAL)
655 return item_b->stype - item_a->stype;
657 /* XXX g_utf8_collate_key() comparisons may speed things
658 * up when having large lists of folders */
659 gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERNAME, &str_a, -1);
660 gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERNAME, &str_b, -1);
662 /* otherwise just compare the folder names */
663 val = g_utf8_collate(str_a, str_b);
671 /* select_function of the gtk tree selection */
672 static gboolean foldercheck_selected(GtkTreeSelection *selection,
673 GtkTreeModel *model, GtkTreePath *path,
674 gboolean currently_selected,gpointer data)
677 FolderItem *item = NULL;
679 if (currently_selected)
682 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
685 gtk_tree_model_get(model, &iter, FOLDERCHECK_FOLDERITEM, &item, -1);
690 /* Callback for the OK-button of the folderselection dialog */
691 static void foldercheck_ok(GtkButton *button, gpointer data)
693 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
695 entry->finished = TRUE;
698 /* Callback for the Cancel-button of the folderselection dialog. Gets also
699 * called on a delete-event of the folderselection window. */
700 static void foldercheck_cancel(GtkButton *button, gpointer data)
702 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
704 entry->cancelled = TRUE;
705 entry->finished = TRUE;
708 /* Set tree of the tree-store. This includes getting the folder tree and
710 static void foldercheck_set_tree(SpecificFolderArrayEntry *entry)
715 for(list = folder_get_list(); list != NULL; list = list->next) {
716 folder = FOLDER(list->data);
719 debug_print("Notification plugin::foldercheck_set_tree(): Found a NULL folder.\n");
723 /* Only regard built-in folders, because folders from plugins (such as RSS, calendar,
724 * or plugin-provided mailbox storage systems like Maildir or MBox) may vanish
725 * without letting us know. */
726 switch(folder->klass->type) {
730 foldercheck_insert_gnode_in_store(entry->tree_store, folder->node, NULL);
737 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry->tree_store),
738 FOLDERCHECK_FOLDERNAME,
741 if(GTK_IS_TREE_VIEW(entry->treeview))
742 gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
745 /* Helper function for foldercheck_set_tree */
746 static void foldercheck_insert_gnode_in_store(GtkTreeStore *store, GNode *node,
753 g_return_if_fail(node != NULL);
754 g_return_if_fail(node->data != NULL);
755 g_return_if_fail(store != NULL);
757 item = FOLDER_ITEM(node->data);
758 foldercheck_append_item(store, item, &child, parent);
760 /* insert its children (this node as parent) */
761 for(iter = node->children; iter != NULL; iter = iter->next)
762 foldercheck_insert_gnode_in_store(store, iter, &child);
765 /* Helper function for foldercheck_insert_gnode_in_store */
766 static void foldercheck_append_item(GtkTreeStore *store, FolderItem *item,
767 GtkTreeIter *iter, GtkTreeIter *parent)
769 gchar *name, *tmpname;
770 GdkPixbuf *pixbuf, *pixbuf_open;
772 PangoWeight weight = PANGO_WEIGHT_NORMAL;
773 GdkColor *foreground = NULL;
774 static GdkColor color_noselect = {0, COLOR_DIM, COLOR_DIM, COLOR_DIM};
776 name = tmpname = folder_item_get_name(item);
778 if (item->stype != F_NORMAL && FOLDER_IS_LOCAL(item->folder)) {
779 switch (item->stype) {
781 if (!strcmp2(item->name, INBOX_DIR))
785 if (!strcmp2(item->name, OUTBOX_DIR))
789 if (!strcmp2(item->name, QUEUE_DIR))
793 if (!strcmp2(item->name, TRASH_DIR))
797 if (!strcmp2(item->name, DRAFT_DIR))
805 if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0) {
806 name = g_strdup_printf("%s (%d)", name, item->total_msgs);
807 } else if (item->unread_msgs > 0) {
808 name = g_strdup_printf("%s (%d)", name, item->unread_msgs);
810 name = g_strdup(name);
812 pixbuf = item->no_select ? foldernoselect_pixbuf : folder_pixbuf;
814 item->no_select ? foldernoselect_pixbuf : folderopen_pixbuf;
816 if (folder_has_parent_of_type(item, F_DRAFT) ||
817 folder_has_parent_of_type(item, F_OUTBOX) ||
818 folder_has_parent_of_type(item, F_TRASH)) {
820 } else if (folder_has_parent_of_type(item, F_QUEUE)) {
821 use_color = (item->total_msgs > 0);
822 if (item->total_msgs > 0)
823 weight = PANGO_WEIGHT_BOLD;
825 if (item->unread_msgs > 0)
826 weight = PANGO_WEIGHT_BOLD;
827 use_color = (item->new_msgs > 0);
831 foreground = &color_noselect;
833 /* insert this node */
834 gtk_tree_store_append(store, iter, parent);
835 gtk_tree_store_set(store, iter,
836 FOLDERCHECK_FOLDERNAME, name,
837 FOLDERCHECK_FOLDERITEM, item,
838 FOLDERCHECK_PIXBUF, pixbuf,
839 FOLDERCHECK_PIXBUF_OPEN, pixbuf_open,
845 /* Callback of the recursive-checkbox */
846 static void foldercheck_recursive_cb(GtkToggleButton *button, gpointer data)
848 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
850 entry->recursive = gtk_toggle_button_get_active(button);
853 /* Callback of the checkboxes corresponding to the folders. Obeys
854 * the "recursive" selection. */
855 static void folder_toggle_cb(GtkCellRendererToggle *cell_renderer,
856 gchar *path_str, gpointer data)
858 gboolean toggle_item;
860 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
861 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
863 gtk_tree_model_get_iter(GTK_TREE_MODEL(entry->tree_store), &iter, path);
864 gtk_tree_path_free(path);
865 gtk_tree_model_get(GTK_TREE_MODEL(entry->tree_store), &iter,
866 FOLDERCHECK_CHECK, &toggle_item, -1);
867 toggle_item = !toggle_item;
869 if(!entry->recursive)
870 gtk_tree_store_set(entry->tree_store, &iter,
871 FOLDERCHECK_CHECK, toggle_item, -1);
874 gtk_tree_store_set(entry->tree_store, &iter,
875 FOLDERCHECK_CHECK, toggle_item, -1);
876 if(gtk_tree_model_iter_children(GTK_TREE_MODEL(entry->tree_store),
878 folder_toggle_recurse_tree(entry->tree_store,&child,
879 FOLDERCHECK_CHECK,toggle_item);
882 while(gtk_events_pending())
883 gtk_main_iteration();
886 /* Helper function for folder_toggle_cb */
887 /* This function calls itself recurively */
888 static void folder_toggle_recurse_tree(GtkTreeStore *tree_store,
889 GtkTreeIter *iterp, gint column,
890 gboolean toggle_item)
892 GtkTreeIter iter = *iterp;
895 /* set the value of this iter */
896 gtk_tree_store_set(tree_store, &iter, column, toggle_item, -1);
898 /* do the same for the first child */
899 if(gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store),&next, &iter))
900 folder_toggle_recurse_tree(tree_store,&next,
901 FOLDERCHECK_CHECK, toggle_item);
903 /* do the same for the next sibling */
904 if(gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &iter))
905 folder_toggle_recurse_tree(tree_store, &iter,
906 FOLDERCHECK_CHECK, toggle_item);
909 /* Helper function to be used with a foreach statement of the model. Checks
910 * if a node is checked, and adds it to a list if it is. data us a (GSList**)
911 * where the result it to be stored */
912 static gboolean foldercheck_foreach_check(GtkTreeModel *model,
914 GtkTreeIter *iter, gpointer data)
916 gboolean toggled_item;
917 GSList **list = (GSList**) data;
919 gtk_tree_model_get(model, iter, FOLDERCHECK_CHECK, &toggled_item, -1);
923 gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
924 *list = g_slist_prepend(*list, item);
930 /* Helper function to be used with a foreach statement of the model. Checks
931 * if a node is checked, and adds it to a list if it is. data us a (GSList**)
932 * where the result it to be stored */
933 static gboolean foldercheck_foreach_update_to_list(GtkTreeModel *model,
938 gchar *ident_tree, *ident_list;
941 gboolean toggle_item = FALSE;
942 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
944 gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
946 if(item->path != NULL)
947 ident_tree = folder_item_get_identifier(item);
951 for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
952 FolderItem *list_item = (FolderItem*) walk->data;
953 ident_list = folder_item_get_identifier(list_item);
954 if(!strcmp2(ident_list,ident_tree)) {
963 gtk_tree_store_set(entry->tree_store, iter, FOLDERCHECK_CHECK,
970 /* Callback for the folder selection dialog. Basically a wrapper around
971 * folder_checked that first resolves the name to an ID first. */
972 void notification_foldercheck_sel_folders_cb(GtkButton *button, gpointer data)
975 gchar *name = (gchar*) data;
977 id = notification_register_folder_specific_list(name);
982 static gboolean my_folder_update_hook(gpointer source, gpointer data)
984 FolderUpdateData *hookdata = (FolderUpdateData*) source;
986 if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) {
988 SpecificFolderArrayEntry *entry;
989 FolderItem *item = hookdata->item;
991 /* If that folder is in anywhere in the array, cut it out. */
992 for(ii = 0; ii < specific_folder_array_size; ii++) {
993 entry = foldercheck_get_entry_from_id(ii);
994 entry->list = g_slist_remove(entry->list, item);
995 } /* for all entries in the array */
996 } /* A FolderItem was deleted */
1001 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
1003 if(event && (event->keyval == GDK_Escape)) {
1004 foldercheck_cancel(NULL, data);