/* Notification plugin for Claws-Mail * Copyright (C) 2005-2007 Holger Berndt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* This code is based on foldersel.c in Claws Mail. * Some functions are only slightly modified, almost 1:1 copies from there. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "pluginconfig.h" /* Basic definitions first */ #include "common/defs.h" /* System includes */ #include #include /* Claws Mail includes */ #include "manage_window.h" #include "folder.h" #include "stock_pixmap.h" #include "gtk/gtkutils.h" #include "common/utils.h" #include "common/prefs.h" #include "common/xml.h" #include "common/hooks.h" #include "gettext.h" /* local includes */ #include "notification_foldercheck.h" /* enums and structures */ enum { FOLDERCHECK_FOLDERNAME, FOLDERCHECK_FOLDERITEM, FOLDERCHECK_PIXBUF, FOLDERCHECK_PIXBUF_OPEN, FOLDERCHECK_CHECK, N_FOLDERCHECK_COLUMNS }; typedef struct { /* Data */ gchar *name; GSList *list; GtkTreeStore *tree_store; /* Dialog box*/ GtkWidget *window; GtkWidget *treeview; gboolean cancelled; gboolean finished; gboolean recursive; } SpecificFolderArrayEntry; /* variables with file scope */ static GdkPixbuf *folder_pixbuf; static GdkPixbuf *folderopen_pixbuf; static GdkPixbuf *foldernoselect_pixbuf; static GArray *specific_folder_array; static guint specific_folder_array_size; static guint hook_folder_update; /* defines */ #define FOLDERCHECK_ARRAY "notification_foldercheck.xml" #define foldercheck_get_entry_from_id(id) \ ((id) < specific_folder_array_size) ? \ g_array_index(specific_folder_array,SpecificFolderArrayEntry*,(id)) : NULL /* function prototypes */ static void folder_checked(guint); static void foldercheck_create_window(SpecificFolderArrayEntry*); static void foldercheck_destroy_window(SpecificFolderArrayEntry*); static gint foldercheck_folder_name_compare(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gpointer); static gboolean foldercheck_selected(GtkTreeSelection*, GtkTreeModel*, GtkTreePath*, gboolean, gpointer); static gint delete_event(GtkWidget*, GdkEventAny*, gpointer); static void foldercheck_ok(GtkButton*, gpointer); static void foldercheck_cancel(GtkButton*, gpointer); static void foldercheck_set_tree(SpecificFolderArrayEntry*); static void foldercheck_insert_gnode_in_store(GtkTreeStore*, GNode*, GtkTreeIter*); static void foldercheck_append_item(GtkTreeStore*, FolderItem*, GtkTreeIter*, GtkTreeIter*); static void foldercheck_recursive_cb(GtkToggleButton*, gpointer); static void folder_toggle_cb(GtkCellRendererToggle*, gchar*, gpointer); static void folder_toggle_recurse_tree(GtkTreeStore*, GtkTreeIter*, gint, gboolean); static gboolean foldercheck_foreach_check(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer); static gboolean foldercheck_foreach_update_to_list(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer); static gchar *foldercheck_get_array_path(void); static gboolean my_folder_update_hook(gpointer, gpointer); /* Creates an entry in the specific_folder_array, and fills it with a new * SpecificFolderArrayEntry*. If specific_folder_array already has an entry * with the same name, return its ID. (The ID is the index in the array.) */ guint notification_register_folder_specific_list(gchar *node_name) { SpecificFolderArrayEntry *entry; gint ii = 0; /* If array does not yet exist, create it. */ if(!specific_folder_array) { specific_folder_array = g_array_new(FALSE, FALSE, sizeof(SpecificFolderArrayEntry*)); specific_folder_array_size = 0; /* Register hook for folder update */ /* "The hook is registered" is bound to "the array is allocated" */ hook_folder_update = hooks_register_hook(FOLDER_UPDATE_HOOKLIST, my_folder_update_hook, NULL); if(hook_folder_update == (guint) -1) { debug_print("Warning: Failed to register hook to folder update " "hooklist. " "Strange things can occur when deleting folders.\n"); } } /* Check if we already have such a name. If so, return its id. */ while(ii < specific_folder_array_size) { entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii); if(entry) { if(!strcmp2(entry->name,node_name)) return ii; } ii++; } /* Create an entry with the corresponding node name. */ entry = g_new(SpecificFolderArrayEntry, 1); entry->name = g_strdup(node_name); entry->list = NULL; entry->window = NULL; entry->treeview = NULL; entry->cancelled = FALSE; entry->finished = FALSE; entry->recursive = FALSE; entry->tree_store = gtk_tree_store_new(N_FOLDERCHECK_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_PIXBUF, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(entry->tree_store), FOLDERCHECK_FOLDERNAME, foldercheck_folder_name_compare, NULL, NULL); specific_folder_array = g_array_append_val(specific_folder_array, entry); return specific_folder_array_size++; } /* This function is called in plugin_done. It frees the whole * folder_specific_array with all its entries. */ void notification_free_folder_specific_array(void) { guint ii; SpecificFolderArrayEntry *entry; for(ii = 0; ii < specific_folder_array_size; ii++) { entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii); if(entry) { g_free(entry->name); if(entry->list) g_slist_free(entry->list); if(entry->tree_store) g_object_unref(G_OBJECT(entry->tree_store)); g_free(entry); } } if(specific_folder_array) { /* Free array */ g_array_free(specific_folder_array, TRUE); /* Unregister hook */ hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_folder_update); } specific_folder_array = NULL; specific_folder_array_size = 0; } /* Returns the list of the entry with the corresponding ID, or NULL if * no such element exists. */ GSList* notification_foldercheck_get_list(guint id) { SpecificFolderArrayEntry *entry; entry = foldercheck_get_entry_from_id(id); if(entry) { return entry->list; } else return NULL; } /* Save selections in a common xml-file. Called when unloading the plugin. * This is analog to folder.h::folder_write_list. */ void notification_foldercheck_write_array(void) { gchar *path; XMLTag *tag; XMLNode *xmlnode; GNode *rootnode; gint ii; PrefFile *pfile; /* Do nothing if foldercheck is not in use */ if(specific_folder_array_size == 0) return; path = foldercheck_get_array_path(); if((pfile = prefs_write_open(path)) == NULL) { debug_print("Notification Plugin Error: Cannot open " "file " FOLDERCHECK_ARRAY " for writing\n"); return; } /* XML declarations */ xml_file_put_xml_decl(pfile->fp); /* Build up XML tree */ /* root node */ tag = xml_tag_new("foldercheckarray"); xmlnode = xml_node_new(tag, NULL); rootnode = g_node_new(xmlnode); /* branch nodes */ for(ii = 0; ii < specific_folder_array_size; ii++) { GNode *branchnode; GSList *walk; SpecificFolderArrayEntry *entry; entry = foldercheck_get_entry_from_id(ii); tag = xml_tag_new("branch"); xml_tag_add_attr(tag, xml_attr_new("name",entry->name)); xmlnode = xml_node_new(tag, NULL); branchnode = g_node_new(xmlnode); g_node_append(rootnode, branchnode); /* Write out the list as leaf nodes */ for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) { gchar *identifier; GNode *node; FolderItem *item = (FolderItem*) walk->data; identifier = folder_item_get_identifier(item); tag = xml_tag_new("folderitem"); xml_tag_add_attr(tag, xml_attr_new("identifier", identifier)); g_free(identifier); xmlnode = xml_node_new(tag, NULL); node = g_node_new(xmlnode); g_node_append(branchnode, node); } /* for all list elements in branch node */ } /* for all branch nodes */ /* Actual writing and cleanup */ xml_write_tree(rootnode, pfile->fp); if(prefs_file_close(pfile) < 0) { debug_print("Notification Plugin Error: Failed to write " "file " FOLDERCHECK_ARRAY "\n"); } /* Free XML tree */ xml_free_tree(rootnode); } /* Read selections from a common xml-file. Called when loading the plugin. * Returns TRUE if data has been read, FALSE if no data is available * or an error occured. * This is analog to folder.h::folder_read_list. */ gboolean notification_foldercheck_read_array(void) { gchar *path; GNode *rootnode, *node, *branchnode; XMLNode *xmlnode; gboolean success = FALSE; path = foldercheck_get_array_path(); if(!is_file_exist(path)) { path = NULL; return FALSE; } /* We don't do merging, so if the file existed, clear what we have stored in memory right now.. */ notification_free_folder_specific_array(); /* .. and evaluate the file */ rootnode = xml_parse_file(path); path = NULL; if(!rootnode) return FALSE; xmlnode = rootnode->data; /* Check that root entry is "foldercheckarray" */ if(strcmp2(xmlnode->tag->tag, "foldercheckarray") != 0) { g_warning("wrong foldercheck array file\n"); xml_free_tree(rootnode); return FALSE; } /* Process branch entries */ for(branchnode = rootnode->children; branchnode != NULL; branchnode = branchnode->next) { GList *list; guint id; SpecificFolderArrayEntry *entry = NULL; xmlnode = branchnode->data; if(strcmp2(xmlnode->tag->tag, "branch") != 0) { g_warning("tag name != \"branch\"\n"); return FALSE; } /* Attributes of the branch nodes */ list = xmlnode->tag->attr; for(; list != NULL; list = list->next) { XMLAttr *attr = list->data; if(attr && attr->name && attr->value && !strcmp2(attr->name, "name")) { id = notification_register_folder_specific_list(attr->value); entry = foldercheck_get_entry_from_id(id); /* We have found something */ success = TRUE; break; } } if((list == NULL) || (entry == NULL)) { g_warning("Did not find attribute \"name\" in tag \"branch\"\n"); continue; /* with next branch */ } /* Now descent into the children of the brach, which are the folderitems */ for(node = branchnode->children; node != NULL; node = node->next) { FolderItem *item = NULL; /* These should all be leaves. */ if(!G_NODE_IS_LEAF(node)) g_warning("Subnodes in \"branch\" nodes should all be leaves. " "Ignoring deeper subnodes..\n"); /* Check if tag is "folderitem" */ xmlnode = node->data; if(strcmp2(xmlnode->tag->tag, "folderitem") != 0) { g_warning("tag name != \"folderitem\"\n"); continue; /* to next node in branch */ } /* Attributes of the leaf nodes */ list = xmlnode->tag->attr; for(; list != NULL; list = list->next) { XMLAttr *attr = list->data; if(attr && attr->name && attr->value && !strcmp2(attr->name, "identifier")) { item = folder_find_item_from_identifier(attr->value); break; } } if((list == NULL) || (item == NULL)) { g_warning("Did not find attribute \"identifier\" in tag " "\"folderitem\"\n"); continue; /* with next leaf node */ } /* Store all FolderItems in the list */ /* We started with a cleared array, so we don't need to check if it's already in there. */ entry->list = g_slist_prepend(entry->list, item); } /* for all subnodes in branch */ } /* for all branches */ return success; } /* Stolen from folder.c. Return value should NOT be freed. */ static gchar *foldercheck_get_array_path(void) { static gchar *filename = NULL; if(!filename) filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FOLDERCHECK_ARRAY, NULL); return filename; } /* Callback for selecting folders. If no selection dialog exists yet, create * one and initialize the selection. The recurse through the whole model, and * add all selected items to the list. */ static void folder_checked(guint id) { SpecificFolderArrayEntry *entry; GSList *checked_list = NULL; entry = foldercheck_get_entry_from_id(id); /* Create window */ foldercheck_create_window(entry); gtk_widget_show(entry->window); manage_window_set_transient(GTK_WINDOW(entry->window)); entry->cancelled = entry->finished = FALSE; while(entry->finished == FALSE) gtk_main_iteration(); foldercheck_destroy_window(entry); if(!entry->cancelled) { /* recurse through the whole model, add all selected items to the list */ gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store), foldercheck_foreach_check, &checked_list); if(entry->list) { g_slist_free(entry->list); entry->list = NULL; } entry->list = g_slist_copy(checked_list); g_slist_free(checked_list); } gtk_tree_store_clear(entry->tree_store); entry->cancelled = FALSE; entry->finished = FALSE; } /* Create the window for selecting folders with checkboxes */ static void foldercheck_create_window(SpecificFolderArrayEntry *entry) { GtkWidget *vbox; GtkWidget *scrolledwin; GtkWidget *confirm_area; GtkWidget *checkbox; GtkWidget *cancel_button; GtkWidget *ok_button; GtkTreeSelection *selection; GtkTreeViewColumn *column; GtkCellRenderer *renderer; static GdkGeometry geometry; /* Create window */ entry->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_foldercheck"); gtk_window_set_title(GTK_WINDOW(entry->window), _("Select folder(s)")); gtk_container_set_border_width(GTK_CONTAINER(entry->window), 4); gtk_window_set_position(GTK_WINDOW(entry->window), GTK_WIN_POS_CENTER); gtk_window_set_modal(GTK_WINDOW(entry->window), TRUE); gtk_window_set_policy(GTK_WINDOW(entry->window), FALSE, TRUE, FALSE); gtk_window_set_wmclass (GTK_WINDOW(entry->window), "folder_selection", "Claws Mail"); g_signal_connect(G_OBJECT(entry->window), "delete_event", G_CALLBACK(delete_event), entry); MANAGE_WINDOW_SIGNALS_CONNECT(entry->window); /* vbox */ vbox = gtk_vbox_new(FALSE, 4); gtk_container_add(GTK_CONTAINER(entry->window), vbox); /* scrolled window */ scrolledwin = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin), GTK_SHADOW_IN); gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0); /* pixbufs */ if(!folder_pixbuf) stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_CLOSE, &folder_pixbuf); if(!folderopen_pixbuf) stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_OPEN, &folderopen_pixbuf); if(!foldernoselect_pixbuf) stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_NOSELECT, &foldernoselect_pixbuf); /* Tree store */ foldercheck_set_tree(entry); gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store), foldercheck_foreach_update_to_list, entry); /* tree view */ entry->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(entry->tree_store)); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(entry->treeview), FALSE); gtk_tree_view_set_search_column(GTK_TREE_VIEW(entry->treeview), FOLDERCHECK_FOLDERNAME); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(entry->treeview)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); gtk_tree_selection_set_select_function(selection, foldercheck_selected, NULL, NULL); gtk_container_add(GTK_CONTAINER(scrolledwin), entry->treeview); /* --- column 1 --- */ column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, "sel"); gtk_tree_view_column_set_spacing(column, 2); /* checkbox */ renderer = gtk_cell_renderer_toggle_new(); g_object_set(renderer, "xalign", 0.0, NULL); gtk_tree_view_column_pack_start(column, renderer, TRUE); g_signal_connect(renderer, "toggled", G_CALLBACK(folder_toggle_cb),entry); gtk_tree_view_column_set_attributes(column, renderer, "active", FOLDERCHECK_CHECK,NULL); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column); /* --- column 2 --- */ column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, "Folder"); gtk_tree_view_column_set_spacing(column, 2); /* pixbuf */ renderer = gtk_cell_renderer_pixbuf_new(); gtk_tree_view_column_pack_start(column, renderer, FALSE); gtk_tree_view_column_set_attributes (column, renderer, "pixbuf", FOLDERCHECK_PIXBUF, "pixbuf-expander-open", FOLDERCHECK_PIXBUF_OPEN, "pixbuf-expander-closed", FOLDERCHECK_PIXBUF, NULL); /* text */ renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_attributes(column, renderer, "text", FOLDERCHECK_FOLDERNAME, NULL); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column); /* recursive */ checkbox = gtk_check_button_new_with_label( _("select recursively")); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), FALSE); g_signal_connect(G_OBJECT(checkbox), "toggled", G_CALLBACK(foldercheck_recursive_cb), entry); gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 10); gtkut_stock_button_set_create(&confirm_area, &cancel_button, GTK_STOCK_CANCEL, &ok_button, GTK_STOCK_OK, NULL, NULL); gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); gtk_widget_grab_default(ok_button); g_signal_connect(G_OBJECT(ok_button), "clicked", G_CALLBACK(foldercheck_ok), entry); g_signal_connect(G_OBJECT(cancel_button), "clicked", G_CALLBACK(foldercheck_cancel), entry); if(!geometry.min_height) { geometry.min_width = 360; geometry.min_height = 360; } gtk_window_set_geometry_hints(GTK_WINDOW(entry->window), NULL, &geometry, GDK_HINT_MIN_SIZE); gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview)); gtk_widget_show_all(vbox); } static void foldercheck_destroy_window(SpecificFolderArrayEntry *entry) { gtk_widget_destroy(entry->window); entry->window = NULL; entry->treeview = NULL; entry->recursive = FALSE; } /* Handler for the delete event of the windows for selecting folders */ static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data) { foldercheck_cancel(NULL, data); return TRUE; } /* sortable_set_sort_func */ static gint foldercheck_folder_name_compare(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer context) { gchar *str_a = NULL, *str_b = NULL; gint val = 0; FolderItem *item_a = NULL, *item_b = NULL; GtkTreeIter parent; gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERITEM, &item_a, -1); gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERITEM, &item_b, -1); /* no sort for root folder */ if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, a)) return 0; /* if both a and b are special folders, sort them according to * their types (which is in-order). Note that this assumes that * there are no multiple folders of a special type. */ if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL) return item_a->stype - item_b->stype; /* if b is normal folder, and a is not, b is smaller (ends up * lower in the list) */ if (item_a->stype != F_NORMAL && item_b->stype == F_NORMAL) return item_b->stype - item_a->stype; /* if b is special folder, and a is not, b is larger (ends up * higher in the list) */ if (item_a->stype == F_NORMAL && item_b->stype != F_NORMAL) return item_b->stype - item_a->stype; /* XXX g_utf8_collate_key() comparisons may speed things * up when having large lists of folders */ gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERNAME, &str_a, -1); gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERNAME, &str_b, -1); /* otherwise just compare the folder names */ val = g_utf8_collate(str_a, str_b); g_free(str_a); g_free(str_b); return val; } /* select_function of the gtk tree selection */ static gboolean foldercheck_selected(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean currently_selected,gpointer data) { GtkTreeIter iter; FolderItem *item = NULL; if (currently_selected) return TRUE; if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path)) return TRUE; gtk_tree_model_get(model, &iter, FOLDERCHECK_FOLDERITEM, &item, -1); return TRUE; } /* Callback for the OK-button of the folderselection dialog */ static void foldercheck_ok(GtkButton *button, gpointer data) { SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data; entry->finished = TRUE; } /* Callback for the Cancel-button of the folderselection dialog. Gets also * called on a delete-event of the folderselection window. */ static void foldercheck_cancel(GtkButton *button, gpointer data) { SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data; entry->cancelled = TRUE; entry->finished = TRUE; } /* Set tree of the tree-store. This includes getting the folder tree and * storing it */ static void foldercheck_set_tree(SpecificFolderArrayEntry *entry) { Folder *folder; GList *list; for(list = folder_get_list(); list != NULL; list = list->next) { folder = FOLDER(list->data); g_return_if_fail(folder != NULL); foldercheck_insert_gnode_in_store(entry->tree_store, folder->node, NULL); } gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry->tree_store), FOLDERCHECK_FOLDERNAME, GTK_SORT_ASCENDING); if(GTK_IS_TREE_VIEW(entry->treeview)) gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview)); } /* Helper function for foldercheck_set_tree */ static void foldercheck_insert_gnode_in_store(GtkTreeStore *store, GNode *node, GtkTreeIter *parent) { FolderItem *item; GtkTreeIter child; GNode *iter; g_return_if_fail(node != NULL); g_return_if_fail(node->data != NULL); g_return_if_fail(store != NULL); item = FOLDER_ITEM(node->data); foldercheck_append_item(store, item, &child, parent); /* insert its children (this node as parent) */ for(iter = node->children; iter != NULL; iter = iter->next) foldercheck_insert_gnode_in_store(store, iter, &child); } /* Helper function for foldercheck_insert_gnode_in_store */ static void foldercheck_append_item(GtkTreeStore *store, FolderItem *item, GtkTreeIter *iter, GtkTreeIter *parent) { gchar *name, *tmpname; GdkPixbuf *pixbuf, *pixbuf_open; gboolean use_color; PangoWeight weight = PANGO_WEIGHT_NORMAL; GdkColor *foreground = NULL; static GdkColor color_noselect = {0, COLOR_DIM, COLOR_DIM, COLOR_DIM}; name = tmpname = folder_item_get_name(item); if (item->stype != F_NORMAL && FOLDER_IS_LOCAL(item->folder)) { switch (item->stype) { case F_INBOX: if (!strcmp2(item->name, INBOX_DIR)) name = "Inbox"; break; case F_OUTBOX: if (!strcmp2(item->name, OUTBOX_DIR)) name = "Sent"; break; case F_QUEUE: if (!strcmp2(item->name, QUEUE_DIR)) name = "Queue"; break; case F_TRASH: if (!strcmp2(item->name, TRASH_DIR)) name = "Trash"; break; case F_DRAFT: if (!strcmp2(item->name, DRAFT_DIR)) name = "Drafts"; break; default: break; } } if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0) { name = g_strdup_printf("%s (%d)", name, item->total_msgs); } else if (item->unread_msgs > 0) { name = g_strdup_printf("%s (%d)", name, item->unread_msgs); } else name = g_strdup(name); pixbuf = item->no_select ? foldernoselect_pixbuf : folder_pixbuf; pixbuf_open = item->no_select ? foldernoselect_pixbuf : folderopen_pixbuf; if (folder_has_parent_of_type(item, F_DRAFT) || folder_has_parent_of_type(item, F_OUTBOX) || folder_has_parent_of_type(item, F_TRASH)) { use_color = FALSE; } else if (folder_has_parent_of_type(item, F_QUEUE)) { use_color = (item->total_msgs > 0); if (item->total_msgs > 0) weight = PANGO_WEIGHT_BOLD; } else { if (item->unread_msgs > 0) weight = PANGO_WEIGHT_BOLD; use_color = (item->new_msgs > 0); } if (item->no_select) foreground = &color_noselect; /* insert this node */ gtk_tree_store_append(store, iter, parent); gtk_tree_store_set(store, iter, FOLDERCHECK_FOLDERNAME, name, FOLDERCHECK_FOLDERITEM, item, FOLDERCHECK_PIXBUF, pixbuf, FOLDERCHECK_PIXBUF_OPEN, pixbuf_open, -1); g_free(tmpname); } /* Callback of the recursive-checkbox */ static void foldercheck_recursive_cb(GtkToggleButton *button, gpointer data) { SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data; entry->recursive = gtk_toggle_button_get_active(button); } /* Callback of the checkboxes corresponding to the folders. Obeys * the "recursive" selection. */ static void folder_toggle_cb(GtkCellRendererToggle *cell_renderer, gchar *path_str, gpointer data) { gboolean toggle_item; GtkTreeIter iter; SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data; GtkTreePath *path = gtk_tree_path_new_from_string(path_str); gtk_tree_model_get_iter(GTK_TREE_MODEL(entry->tree_store), &iter, path); gtk_tree_path_free(path); gtk_tree_model_get(GTK_TREE_MODEL(entry->tree_store), &iter, FOLDERCHECK_CHECK, &toggle_item, -1); toggle_item = !toggle_item; if(!entry->recursive) gtk_tree_store_set(entry->tree_store, &iter, FOLDERCHECK_CHECK, toggle_item, -1); else { GtkTreeIter child; gtk_tree_store_set(entry->tree_store, &iter, FOLDERCHECK_CHECK, toggle_item, -1); if(gtk_tree_model_iter_children(GTK_TREE_MODEL(entry->tree_store), &child, &iter)) folder_toggle_recurse_tree(entry->tree_store,&child, FOLDERCHECK_CHECK,toggle_item); } } /* Helper function for folder_toggle_cb */ /* This function calls itself recurively */ static void folder_toggle_recurse_tree(GtkTreeStore *tree_store, GtkTreeIter *iterp, gint column, gboolean toggle_item) { GtkTreeIter iter = *iterp; GtkTreeIter next; /* set the value of this iter */ gtk_tree_store_set(tree_store, &iter, column, toggle_item, -1); /* do the same for the first child */ if(gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store),&next, &iter)) folder_toggle_recurse_tree(tree_store,&next, FOLDERCHECK_CHECK, toggle_item); /* do the same for the next sibling */ if(gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &iter)) folder_toggle_recurse_tree(tree_store, &iter, FOLDERCHECK_CHECK, toggle_item); } /* Helper function to be used with a foreach statement of the model. Checks * if a node is checked, and adds it to a list if it is. data us a (GSList**) * where the result it to be stored */ static gboolean foldercheck_foreach_check(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { gboolean toggled_item; GSList **list = (GSList**) data; gtk_tree_model_get(model, iter, FOLDERCHECK_CHECK, &toggled_item, -1); if(toggled_item) { FolderItem *item; gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1); *list = g_slist_prepend(*list, item); } return FALSE; } /* Helper function to be used with a foreach statement of the model. Checks * if a node is checked, and adds it to a list if it is. data us a (GSList**) * where the result it to be stored */ static gboolean foldercheck_foreach_update_to_list(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { gchar *ident_tree, *ident_list; FolderItem *item; GSList *walk; gboolean toggle_item = FALSE; SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data; gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1); if(item->path != NULL) ident_tree = folder_item_get_identifier(item); else return FALSE; for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) { FolderItem *list_item = (FolderItem*) walk->data; ident_list = folder_item_get_identifier(list_item); if(!strcmp2(ident_list,ident_tree)) { toggle_item = TRUE; g_free(ident_list); break; } g_free(ident_list); } g_free(ident_tree); gtk_tree_store_set(entry->tree_store, iter, FOLDERCHECK_CHECK, toggle_item, -1); return FALSE; } /* Callback for the folder selection dialog. Basically a wrapper around * folder_checked that first resolves the name to an ID first. */ void notification_foldercheck_sel_folders_cb(GtkButton *button, gpointer data) { guint id; gchar *name = (gchar*) data; id = notification_register_folder_specific_list(name); folder_checked(id); } static gboolean my_folder_update_hook(gpointer source, gpointer data) { FolderUpdateData *hookdata = (FolderUpdateData*) source; if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) { gint ii; SpecificFolderArrayEntry *entry; FolderItem *item = hookdata->item; /* If that folder is in anywhere in the array, cut it out. */ for(ii = 0; ii < specific_folder_array_size; ii++) { entry = foldercheck_get_entry_from_id(ii); entry->list = g_slist_remove(entry->list, item); } /* for all entries in the array */ } /* A FolderItem was deleted */ return FALSE; }