/* 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 <http://www.gnu.org/licenses/>.
*/
/* 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 <string.h>
#include <gtk/gtk.h>
/* 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;
}
syntax highlighted by Code2HTML, v. 0.9.1