Fix bug #3380. Initialize widget before callback handler for 'clicked'
[claws.git] / src / plugins / notification / notification_foldercheck.c
1
2 /* Notification plugin for Claws-Mail
3  * Copyright (C) 2005-2007 Holger Berndt
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 /* This code is based on foldersel.c in Claws Mail.
20  * Some functions are only slightly modified, almost 1:1 copies from there. */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #  include "claws-features.h"
25 #endif
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29
30 /* Basic definitions first */
31 #include "common/defs.h"
32
33 /* System includes */
34 #include <string.h>
35 #include <gdk/gdkkeysyms.h>
36 #include <gtk/gtk.h>
37
38 /* Claws Mail includes */
39 #include "manage_window.h"
40 #include "folder.h"
41 #include "stock_pixmap.h"
42 #include "gtk/gtkutils.h"
43 #include "common/utils.h"
44 #include "common/prefs.h"
45 #include "common/xml.h"
46 #include "common/hooks.h"
47 #include "prefs_common.h"
48
49 /* local includes */
50 #include "notification_foldercheck.h"
51
52 /* enums and structures */
53 enum {
54   FOLDERCHECK_FOLDERNAME,
55   FOLDERCHECK_FOLDERITEM,
56   FOLDERCHECK_PIXBUF,
57   FOLDERCHECK_PIXBUF_OPEN,
58   FOLDERCHECK_CHECK,
59   N_FOLDERCHECK_COLUMNS
60 };
61
62 typedef struct {
63   /* Data */
64   gchar        *name;
65   GSList       *list;
66   GtkTreeStore *tree_store;
67   /* Dialog box*/
68   GtkWidget *window;
69   GtkWidget *treeview;
70   gboolean cancelled;
71   gboolean finished;
72   gboolean recursive;
73 } SpecificFolderArrayEntry;
74
75 /* variables with file scope */ 
76 static GdkPixbuf *folder_pixbuf;
77 static GdkPixbuf *folderopen_pixbuf;
78 static GdkPixbuf *foldernoselect_pixbuf;
79
80 static GArray *specific_folder_array;
81 static guint   specific_folder_array_size;
82
83 static guint hook_folder_update;
84
85
86 /* defines */
87 #define FOLDERCHECK_ARRAY "notification_foldercheck.xml"
88 #define foldercheck_get_entry_from_id(id) \
89   ((id) < specific_folder_array_size) ? \
90   g_array_index(specific_folder_array,SpecificFolderArrayEntry*,(id)) : NULL
91
92 /* function prototypes */
93 static void folder_checked(guint);
94 static void foldercheck_create_window(SpecificFolderArrayEntry*);
95 static void foldercheck_destroy_window(SpecificFolderArrayEntry*);
96 static gint foldercheck_folder_name_compare(GtkTreeModel*, GtkTreeIter*,
97                                             GtkTreeIter*, gpointer);
98 static gboolean foldercheck_selected(GtkTreeSelection*,
99                                      GtkTreeModel*, GtkTreePath*,
100                                      gboolean, gpointer);
101
102 static gint delete_event(GtkWidget*, GdkEventAny*, gpointer);
103 static void foldercheck_ok(GtkButton*, gpointer);
104 static void foldercheck_cancel(GtkButton*, gpointer);
105 static void foldercheck_set_tree(SpecificFolderArrayEntry*);
106 static void foldercheck_insert_gnode_in_store(GtkTreeStore*, GNode*,
107                                               GtkTreeIter*);
108 static void foldercheck_append_item(GtkTreeStore*, FolderItem*,
109                                     GtkTreeIter*, GtkTreeIter*);
110 static void foldercheck_recursive_cb(GtkToggleButton*, gpointer);
111 static void folder_toggle_cb(GtkCellRendererToggle*, gchar*, gpointer);
112 static void folder_toggle_recurse_tree(GtkTreeStore*, GtkTreeIter*, gint,
113                                        gboolean);
114 static gboolean foldercheck_foreach_check(GtkTreeModel*, GtkTreePath*,
115                                           GtkTreeIter*, gpointer);
116 static gboolean foldercheck_foreach_update_to_list(GtkTreeModel*, GtkTreePath*,
117                                                    GtkTreeIter*, gpointer);
118 static gchar *foldercheck_get_array_path(void);
119 static gboolean my_folder_update_hook(gpointer, gpointer);
120 static gboolean key_pressed(GtkWidget*, GdkEventKey*,gpointer);
121
122
123 /* Creates an entry in the specific_folder_array, and fills it with a new
124  * SpecificFolderArrayEntry*. If specific_folder_array already has an entry
125  * with the same name, return its ID. (The ID is the index in the array.) */
126 guint notification_register_folder_specific_list(gchar *node_name)
127 {
128   SpecificFolderArrayEntry *entry;
129   gint ii = 0;
130
131   /* If array does not yet exist, create it. */
132   if(!specific_folder_array) {
133     specific_folder_array = g_array_new(FALSE, FALSE,
134                                         sizeof(SpecificFolderArrayEntry*));
135     specific_folder_array_size = 0;
136
137     /* Register hook for folder update */
138     /* "The hook is registered" is bound to "the array is allocated" */
139     hook_folder_update = hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
140                                              my_folder_update_hook, NULL);
141     if(hook_folder_update == (guint) -1) {
142       debug_print("Warning: Failed to register hook to folder update "
143                   "hooklist. "
144                   "Strange things can occur when deleting folders.\n");
145     }
146   }
147
148   /* Check if we already have such a name. If so, return its id. */
149   while(ii < specific_folder_array_size) {
150     entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
151     if(entry) {
152       if(!strcmp2(entry->name,node_name))
153         return ii;
154     }
155     ii++;
156   }
157
158   /* Create an entry with the corresponding node name. */
159   entry = g_new(SpecificFolderArrayEntry, 1);
160   entry->name = g_strdup(node_name);
161   entry->list = NULL;
162   entry->window = NULL;
163   entry->treeview = NULL;
164   entry->cancelled = FALSE;
165   entry->finished  = FALSE;
166   entry->recursive = FALSE;
167   entry->tree_store = gtk_tree_store_new(N_FOLDERCHECK_COLUMNS,
168                                          G_TYPE_STRING,
169                                          G_TYPE_POINTER,
170                                          GDK_TYPE_PIXBUF,
171                                          GDK_TYPE_PIXBUF,
172                                          G_TYPE_BOOLEAN);
173   gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(entry->tree_store),
174                                   FOLDERCHECK_FOLDERNAME,
175                                   foldercheck_folder_name_compare,
176                                   NULL, NULL);
177   specific_folder_array = g_array_append_val(specific_folder_array, entry);
178   return specific_folder_array_size++;
179 }
180
181 /* This function is called in plugin_done. It frees the whole
182  * folder_specific_array with all its entries. */
183 void notification_free_folder_specific_array(void)
184 {
185   guint ii;
186   SpecificFolderArrayEntry *entry;
187
188   for(ii = 0; ii < specific_folder_array_size; ii++) {
189     entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
190     if(entry) {
191       g_free(entry->name);
192       if(entry->list)
193         g_slist_free(entry->list);
194       if(entry->tree_store)
195         g_object_unref(G_OBJECT(entry->tree_store));
196       g_free(entry);
197     }
198   }
199   if(specific_folder_array) {
200     /* Free array */
201     g_array_free(specific_folder_array, TRUE);
202
203     /* Unregister hook */
204     hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_folder_update);
205   }
206   specific_folder_array = NULL;
207   specific_folder_array_size = 0;
208 }
209
210 /* Returns the list of the entry with the corresponding ID, or NULL if
211  * no such element exists. */
212 GSList* notification_foldercheck_get_list(guint id)
213 {
214   SpecificFolderArrayEntry *entry;
215   
216   entry = foldercheck_get_entry_from_id(id);
217   if(entry) {
218     return entry->list;
219   }
220   else
221     return NULL;
222 }
223
224 /* Save selections in a common xml-file. Called when unloading the plugin.
225  * This is analog to folder.h::folder_write_list. */
226 void notification_foldercheck_write_array(void)
227 {
228   gchar *path;
229   XMLTag *tag;
230   XMLNode *xmlnode;
231   GNode *rootnode;
232   gint ii;
233   PrefFile *pfile;
234
235   /* Do nothing if foldercheck is not in use */
236   if(specific_folder_array_size == 0)
237     return;
238
239   path = foldercheck_get_array_path();
240   if((pfile = prefs_write_open(path)) == NULL) {
241     debug_print("Notification Plugin Error: Cannot open "
242                 "file " FOLDERCHECK_ARRAY " for writing\n");
243     return;
244   }
245
246   /* XML declarations */
247   xml_file_put_xml_decl(pfile->fp);
248
249   /* Build up XML tree */
250   
251   /* root node */
252   tag = xml_tag_new("foldercheckarray");
253   xmlnode = xml_node_new(tag, NULL);
254   rootnode = g_node_new(xmlnode);
255
256   /* branch nodes */
257   for(ii = 0; ii < specific_folder_array_size; ii++) {
258     GNode *branchnode;
259     GSList *walk;
260     SpecificFolderArrayEntry *entry;
261   
262     entry = foldercheck_get_entry_from_id(ii);
263     
264     tag = xml_tag_new("branch");
265     xml_tag_add_attr(tag, xml_attr_new("name",entry->name));
266     xmlnode = xml_node_new(tag, NULL);
267     branchnode = g_node_new(xmlnode);
268     g_node_append(rootnode, branchnode);
269
270     /* Write out the list as leaf nodes */
271     for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
272       gchar *identifier;
273       GNode *node;
274       FolderItem *item = (FolderItem*) walk->data;
275
276       identifier = folder_item_get_identifier(item);
277
278       tag = xml_tag_new("folderitem");
279       xml_tag_add_attr(tag, xml_attr_new("identifier", identifier));
280       g_free(identifier);
281       xmlnode = xml_node_new(tag, NULL);
282       node = g_node_new(xmlnode);
283       g_node_append(branchnode, node);
284     } /* for all list elements in branch node */
285
286   } /* for all branch nodes */
287
288   /* Actual writing and cleanup */
289   xml_write_tree(rootnode, pfile->fp);
290
291   if(prefs_file_close(pfile) < 0) {
292     debug_print("Notification Plugin Error: Failed to write "
293                 "file " FOLDERCHECK_ARRAY "\n");
294   }
295
296   /* Free XML tree */
297   xml_free_tree(rootnode);
298 }
299
300 /* Read selections from a common xml-file. Called when loading the plugin.
301  * Returns TRUE if data has been read, FALSE if no data is available
302  * or an error occured.
303  * This is analog to folder.h::folder_read_list. */
304 gboolean notification_foldercheck_read_array(void)
305 {
306   gchar *path;
307   GNode *rootnode, *node, *branchnode;
308   XMLNode *xmlnode;
309   gboolean success = FALSE;
310
311   path = foldercheck_get_array_path();
312   if(!is_file_exist(path)) {
313     path = NULL;
314     return FALSE;
315   }
316
317   /* We don't do merging, so if the file existed, clear what we
318      have stored in memory right now.. */
319   notification_free_folder_specific_array();
320
321   /* .. and evaluate the file */
322   rootnode = xml_parse_file(path);
323   path = NULL;
324   if(!rootnode)
325     return FALSE;
326
327   xmlnode = rootnode->data;
328
329   /* Check that root entry is "foldercheckarray" */
330   if(strcmp2(xmlnode->tag->tag, "foldercheckarray") != 0) {
331     g_warning("wrong foldercheck array file\n");
332     xml_free_tree(rootnode);
333     return FALSE;
334   }
335
336   /* Process branch entries */
337   for(branchnode = rootnode->children; branchnode != NULL;
338       branchnode = branchnode->next) {
339     GList *list;
340     guint id;
341     SpecificFolderArrayEntry *entry = NULL;
342
343     xmlnode = branchnode->data;
344     if(strcmp2(xmlnode->tag->tag, "branch") != 0) {
345       g_warning("tag name != \"branch\"\n");
346       return FALSE;
347     }
348
349     /* Attributes of the branch nodes */
350     list = xmlnode->tag->attr;
351     for(; list != NULL; list = list->next) {
352       XMLAttr *attr = list->data;
353
354       if(attr && attr->name && attr->value &&  !strcmp2(attr->name, "name")) {
355         id = notification_register_folder_specific_list(attr->value);
356         entry = foldercheck_get_entry_from_id(id);
357         /* We have found something */
358         success = TRUE;
359         break;
360       }
361     }
362     if((list == NULL) || (entry == NULL)) {
363       g_warning("Did not find attribute \"name\" in tag \"branch\"\n");
364       continue; /* with next branch */
365     }
366
367     /* Now descent into the children of the brach, which are the folderitems */
368     for(node = branchnode->children; node != NULL; node = node->next) {
369       FolderItem *item = NULL;
370
371       /* These should all be leaves. */
372       if(!G_NODE_IS_LEAF(node))
373         g_warning("Subnodes in \"branch\" nodes should all be leaves. "
374                   "Ignoring deeper subnodes..\n");
375
376       /* Check if tag is "folderitem" */
377       xmlnode = node->data;
378       if(strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
379         g_warning("tag name != \"folderitem\"\n");
380         continue; /* to next node in branch */
381       }
382
383       /* Attributes of the leaf nodes */
384       list = xmlnode->tag->attr;
385       for(; list != NULL; list = list->next) {
386         XMLAttr *attr = list->data;
387
388         if(attr && attr->name && attr->value &&
389            !strcmp2(attr->name, "identifier")) {
390           item = folder_find_item_from_identifier(attr->value);
391           break;
392         }
393       }
394       if((list == NULL) || (item == NULL)) {
395         g_warning("Did not find attribute \"identifier\" in tag "
396                   "\"folderitem\"\n");
397         continue; /* with next leaf node */
398       }
399       
400       /* Store all FolderItems in the list */
401       /* We started with a cleared array, so we don't need to check if
402          it's already in there. */
403       entry->list = g_slist_prepend(entry->list, item);
404
405     } /* for all subnodes in branch */
406
407   } /* for all branches */
408   return success;
409 }
410
411 /* Stolen from folder.c. Return value should NOT be freed. */
412 static gchar *foldercheck_get_array_path(void)
413 {
414   static gchar *filename = NULL;
415
416   if(!filename)
417     filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
418                            FOLDERCHECK_ARRAY, NULL);
419   return filename;
420 }
421
422 /* Callback for selecting folders. If no selection dialog exists yet, create
423  * one and initialize the selection. The recurse through the whole model, and
424  * add all selected items to the list. */
425 static void folder_checked(guint id)
426 {
427   SpecificFolderArrayEntry *entry;
428   GSList *checked_list = NULL;
429
430   entry = foldercheck_get_entry_from_id(id);
431
432   /* Create window */
433   foldercheck_create_window(entry);
434   gtk_widget_show(entry->window);
435   manage_window_set_transient(GTK_WINDOW(entry->window));
436
437   entry->cancelled = entry->finished = FALSE;
438   while(entry->finished == FALSE)
439     gtk_main_iteration();
440   
441   foldercheck_destroy_window(entry);
442
443   if(!entry->cancelled) {
444     /* recurse through the whole model, add all selected items to the list */
445     gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
446                            foldercheck_foreach_check, &checked_list);
447
448     if(entry->list) {
449       g_slist_free(entry->list);
450       entry->list = NULL;
451     }
452     entry->list = g_slist_copy(checked_list);
453     g_slist_free(checked_list);
454   }
455
456   gtk_tree_store_clear(entry->tree_store);
457
458   entry->cancelled = FALSE;
459   entry->finished  = FALSE;
460 }
461
462 /* Create the window for selecting folders with checkboxes */
463 static void foldercheck_create_window(SpecificFolderArrayEntry *entry)
464 {
465   GtkWidget *vbox;
466   GtkWidget *scrolledwin;
467   GtkWidget *confirm_area;
468   GtkWidget *checkbox;
469   GtkWidget *cancel_button;
470   GtkWidget *ok_button;
471   GtkTreeSelection *selection;
472   GtkTreeViewColumn *column;
473   GtkCellRenderer *renderer;
474   static GdkGeometry geometry;
475
476   /* Create window */
477   entry->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_foldercheck");
478   gtk_window_set_title(GTK_WINDOW(entry->window), _("Select folder(s)"));
479   gtk_container_set_border_width(GTK_CONTAINER(entry->window), 4);
480   gtk_window_set_position(GTK_WINDOW(entry->window), GTK_WIN_POS_CENTER);
481   gtk_window_set_modal(GTK_WINDOW(entry->window), TRUE);
482   gtk_window_set_resizable(GTK_WINDOW(entry->window), TRUE);
483   gtk_window_set_wmclass
484     (GTK_WINDOW(entry->window), "folder_selection", "Claws Mail");  
485   g_signal_connect(G_OBJECT(entry->window), "delete_event",
486                    G_CALLBACK(delete_event), entry);
487   g_signal_connect(G_OBJECT(entry->window), "key_press_event",
488       G_CALLBACK(key_pressed), entry);
489   MANAGE_WINDOW_SIGNALS_CONNECT(entry->window);
490
491   /* vbox */
492   vbox = gtk_vbox_new(FALSE, 4);
493   gtk_container_add(GTK_CONTAINER(entry->window), vbox);
494
495   /* scrolled window */
496   scrolledwin = gtk_scrolled_window_new(NULL, NULL);
497   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
498                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
499   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
500                                       GTK_SHADOW_IN);
501   gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
502
503   /* pixbufs */
504   if(!folder_pixbuf)
505     stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_CLOSE,
506                      &folder_pixbuf);
507   if(!folderopen_pixbuf)
508     stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_OPEN,
509                      &folderopen_pixbuf);
510   if(!foldernoselect_pixbuf)
511     stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_NOSELECT,
512                      &foldernoselect_pixbuf);
513
514   /* Tree store */
515   foldercheck_set_tree(entry);
516   gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
517                          foldercheck_foreach_update_to_list, entry);
518
519
520   /* tree view */
521   entry->treeview =
522     gtk_tree_view_new_with_model(GTK_TREE_MODEL(entry->tree_store));
523   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(entry->treeview), FALSE);
524   gtk_tree_view_set_search_column(GTK_TREE_VIEW(entry->treeview),
525                                   FOLDERCHECK_FOLDERNAME);
526   gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(entry->treeview),
527                                   prefs_common_get_prefs()->use_stripes_everywhere);
528   gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(entry->treeview), FALSE);
529
530   selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(entry->treeview));
531   gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
532   gtk_tree_selection_set_select_function(selection, foldercheck_selected,
533                                          NULL, NULL);
534
535   gtk_container_add(GTK_CONTAINER(scrolledwin), entry->treeview);
536
537   /* --- column 1 --- */
538   column = gtk_tree_view_column_new();
539   gtk_tree_view_column_set_title(column, "sel");
540   gtk_tree_view_column_set_spacing(column, 2);
541   
542   /* checkbox */
543   renderer = gtk_cell_renderer_toggle_new();
544   g_object_set(renderer, "xalign", 0.0, NULL);
545   gtk_tree_view_column_pack_start(column, renderer, TRUE);
546   g_signal_connect(renderer, "toggled", G_CALLBACK(folder_toggle_cb),entry);
547   gtk_tree_view_column_set_attributes(column, renderer,
548                                       "active", FOLDERCHECK_CHECK,NULL);
549
550   gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
551   gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
552
553   /* --- column 2 --- */
554   column = gtk_tree_view_column_new();
555   gtk_tree_view_column_set_title(column, "Folder");
556   gtk_tree_view_column_set_spacing(column, 2);
557
558   /* pixbuf */
559   renderer = gtk_cell_renderer_pixbuf_new();
560   gtk_tree_view_column_pack_start(column, renderer, FALSE);
561   gtk_tree_view_column_set_attributes
562     (column, renderer,
563      "pixbuf", FOLDERCHECK_PIXBUF,
564      "pixbuf-expander-open", FOLDERCHECK_PIXBUF_OPEN,
565      "pixbuf-expander-closed", FOLDERCHECK_PIXBUF,
566      NULL);
567
568   /* text */
569   renderer = gtk_cell_renderer_text_new();
570   gtk_tree_view_column_pack_start(column, renderer, TRUE);
571   gtk_tree_view_column_set_attributes(column, renderer,
572                                       "text", FOLDERCHECK_FOLDERNAME,
573                                       NULL);
574
575   gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
576   gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
577
578   /* recursive */
579   checkbox = gtk_check_button_new_with_label( _("select recursively"));
580   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), FALSE);
581   g_signal_connect(G_OBJECT(checkbox), "toggled",
582                    G_CALLBACK(foldercheck_recursive_cb), entry);
583   gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 10);
584
585   gtkut_stock_button_set_create(&confirm_area,
586                                 &cancel_button, GTK_STOCK_CANCEL,
587                                 &ok_button,     GTK_STOCK_OK,
588                                 NULL,           NULL);
589   gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
590   gtk_widget_grab_default(ok_button);
591
592   g_signal_connect(G_OBJECT(ok_button), "clicked",
593                    G_CALLBACK(foldercheck_ok), entry);
594   g_signal_connect(G_OBJECT(cancel_button), "clicked",
595                    G_CALLBACK(foldercheck_cancel), entry);
596
597   if(!geometry.min_height) {
598     geometry.min_width = 360;
599     geometry.min_height = 360;
600   }
601
602   gtk_window_set_geometry_hints(GTK_WINDOW(entry->window), NULL, &geometry,
603                                 GDK_HINT_MIN_SIZE);
604
605   gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
606
607   gtk_widget_show_all(vbox);
608 }
609
610 static void foldercheck_destroy_window(SpecificFolderArrayEntry *entry)
611 {
612   gtk_widget_destroy(entry->window);
613   entry->window = NULL;
614   entry->treeview = NULL;
615   entry->recursive = FALSE;
616 }
617
618 /* Handler for the delete event of the windows for selecting folders */
619 static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data)
620 {
621   foldercheck_cancel(NULL, data);
622   return TRUE;
623 }
624
625 /* sortable_set_sort_func */
626 static gint foldercheck_folder_name_compare(GtkTreeModel *model,
627                                             GtkTreeIter *a, GtkTreeIter *b,
628                                             gpointer context)
629 {
630   gchar *str_a = NULL, *str_b = NULL;
631   gint val = 0;
632   FolderItem *item_a = NULL, *item_b = NULL;
633   GtkTreeIter parent;
634
635   gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERITEM, &item_a, -1);
636   gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERITEM, &item_b, -1);
637
638   /* no sort for root folder */
639   if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, a))
640     return 0;
641
642   /* if both a and b are special folders, sort them according to
643    * their types (which is in-order). Note that this assumes that
644    * there are no multiple folders of a special type. */
645   if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL)
646     return item_a->stype - item_b->stype;
647
648   /* if b is normal folder, and a is not, b is smaller (ends up
649    * lower in the list) */
650   if (item_a->stype != F_NORMAL && item_b->stype == F_NORMAL)
651     return item_b->stype - item_a->stype;
652
653   /* if b is special folder, and a is not, b is larger (ends up
654    * higher in the list) */
655   if (item_a->stype == F_NORMAL && item_b->stype != F_NORMAL)
656     return item_b->stype - item_a->stype;
657
658   /* XXX g_utf8_collate_key() comparisons may speed things
659    * up when having large lists of folders */
660   gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERNAME, &str_a, -1);
661   gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERNAME, &str_b, -1);
662
663   /* otherwise just compare the folder names */
664   val = g_utf8_collate(str_a, str_b);
665   
666   g_free(str_a);
667   g_free(str_b);
668
669   return val;
670 }
671
672 /* select_function of the gtk tree selection */
673 static gboolean foldercheck_selected(GtkTreeSelection *selection,
674                                      GtkTreeModel *model, GtkTreePath *path,
675                                      gboolean currently_selected,gpointer data)
676 {
677   GtkTreeIter iter;
678   FolderItem *item = NULL;
679
680   if (currently_selected)
681     return TRUE;
682
683   if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
684     return TRUE;
685
686   gtk_tree_model_get(model, &iter, FOLDERCHECK_FOLDERITEM, &item, -1);
687
688   return TRUE;
689 }
690
691 /* Callback for the OK-button of the folderselection dialog */
692 static void foldercheck_ok(GtkButton *button, gpointer data)
693 {
694   SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
695
696   entry->finished = TRUE;
697 }
698
699 /* Callback for the Cancel-button of the folderselection dialog. Gets also
700  * called on a delete-event of the folderselection window. */
701 static void foldercheck_cancel(GtkButton *button, gpointer data)
702 {
703   SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
704
705   entry->cancelled = TRUE;
706   entry->finished  = TRUE;
707 }
708
709 /* Set tree of the tree-store. This includes getting the folder tree and
710  * storing it */
711 static void foldercheck_set_tree(SpecificFolderArrayEntry *entry)
712 {
713   Folder *folder;
714   GList *list;
715
716   for(list = folder_get_list(); list != NULL; list = list->next) {
717     folder = FOLDER(list->data);
718
719     if(folder == NULL) {
720       debug_print("Notification plugin::foldercheck_set_tree(): Found a NULL folder.\n");
721       continue;
722     }
723
724     /* Only regard built-in folders, because folders from plugins (such as RSS, calendar,
725      * or plugin-provided mailbox storage systems like Maildir or MBox) may vanish
726      * without letting us know. */
727     switch(folder->klass->type) {
728     case F_MH:
729     case F_IMAP:
730     case F_NEWS:
731       foldercheck_insert_gnode_in_store(entry->tree_store, folder->node, NULL);
732       break;
733     default:
734       break;
735     }
736   }
737
738   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry->tree_store),
739                                        FOLDERCHECK_FOLDERNAME,
740                                        GTK_SORT_ASCENDING);
741
742   if(GTK_IS_TREE_VIEW(entry->treeview))
743     gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
744 }
745
746 /* Helper function for foldercheck_set_tree */
747 static void foldercheck_insert_gnode_in_store(GtkTreeStore *store, GNode *node,
748                                               GtkTreeIter *parent)
749 {
750   FolderItem *item;
751   GtkTreeIter child;
752   GNode *iter;
753
754   g_return_if_fail(node != NULL);
755   g_return_if_fail(node->data != NULL);
756   g_return_if_fail(store != NULL);
757
758   item = FOLDER_ITEM(node->data);
759   foldercheck_append_item(store, item, &child, parent);
760
761   /* insert its children (this node as parent) */
762   for(iter = node->children; iter != NULL; iter = iter->next)
763     foldercheck_insert_gnode_in_store(store, iter, &child);
764 }
765
766 /* Helper function for foldercheck_insert_gnode_in_store */
767 static void foldercheck_append_item(GtkTreeStore *store, FolderItem *item,
768                                     GtkTreeIter *iter, GtkTreeIter *parent)
769 {
770   gchar *name, *tmpname;
771   GdkPixbuf *pixbuf, *pixbuf_open;
772
773   name = tmpname = folder_item_get_name(item);
774
775   if (item->stype != F_NORMAL && FOLDER_IS_LOCAL(item->folder)) {
776     switch (item->stype) {
777     case F_INBOX:
778       if (!strcmp2(item->name, INBOX_DIR))
779         name = "Inbox";
780       break;
781     case F_OUTBOX:
782       if (!strcmp2(item->name, OUTBOX_DIR))
783         name = "Sent";
784       break;
785     case F_QUEUE:
786       if (!strcmp2(item->name, QUEUE_DIR))
787         name = "Queue";
788       break;
789     case F_TRASH:
790       if (!strcmp2(item->name, TRASH_DIR))
791         name = "Trash";
792       break;
793     case F_DRAFT:
794       if (!strcmp2(item->name, DRAFT_DIR))
795         name = "Drafts";
796       break;
797     default:
798       break;
799     }
800   }
801
802   if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0) {
803     name = g_strdup_printf("%s (%d)", name, item->total_msgs);
804   } else if (item->unread_msgs > 0) {
805     name = g_strdup_printf("%s (%d)", name, item->unread_msgs);
806   } else
807     name = g_strdup(name);
808   
809   pixbuf = item->no_select ? foldernoselect_pixbuf : folder_pixbuf;
810   pixbuf_open =
811     item->no_select ? foldernoselect_pixbuf : folderopen_pixbuf;
812   
813   /* insert this node */
814   gtk_tree_store_append(store, iter, parent);
815   gtk_tree_store_set(store, iter,
816                      FOLDERCHECK_FOLDERNAME, name,
817                      FOLDERCHECK_FOLDERITEM, item,
818                      FOLDERCHECK_PIXBUF, pixbuf,
819                      FOLDERCHECK_PIXBUF_OPEN, pixbuf_open,
820                      -1);
821
822   g_free(tmpname);
823 }
824
825 /* Callback of the recursive-checkbox */
826 static void foldercheck_recursive_cb(GtkToggleButton *button, gpointer data)
827 {
828   SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
829
830   entry->recursive = gtk_toggle_button_get_active(button);
831 }
832
833 /* Callback of the checkboxes corresponding to the folders. Obeys
834  * the "recursive" selection. */
835 static void folder_toggle_cb(GtkCellRendererToggle *cell_renderer,
836                              gchar *path_str, gpointer data)
837 {
838   gboolean toggle_item;
839   GtkTreeIter iter;
840   SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
841   GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
842
843   gtk_tree_model_get_iter(GTK_TREE_MODEL(entry->tree_store), &iter, path);
844   gtk_tree_path_free(path);
845   gtk_tree_model_get(GTK_TREE_MODEL(entry->tree_store), &iter,
846                      FOLDERCHECK_CHECK, &toggle_item, -1);
847   toggle_item = !toggle_item;
848
849   if(!entry->recursive)
850     gtk_tree_store_set(entry->tree_store, &iter,
851                        FOLDERCHECK_CHECK, toggle_item, -1);
852   else {
853     GtkTreeIter child;
854     gtk_tree_store_set(entry->tree_store, &iter,
855                        FOLDERCHECK_CHECK, toggle_item, -1);
856     if(gtk_tree_model_iter_children(GTK_TREE_MODEL(entry->tree_store),
857                                     &child, &iter))
858       folder_toggle_recurse_tree(entry->tree_store,&child,
859                                  FOLDERCHECK_CHECK,toggle_item);
860   }
861
862   while(gtk_events_pending())
863     gtk_main_iteration();
864 }
865
866 /* Helper function for folder_toggle_cb */
867 /* This function calls itself recurively */
868 static void folder_toggle_recurse_tree(GtkTreeStore *tree_store,
869                                        GtkTreeIter *iterp, gint column,
870                                        gboolean toggle_item)
871 {
872   GtkTreeIter iter = *iterp;
873   GtkTreeIter next;
874
875   /* set the value of this iter */
876   gtk_tree_store_set(tree_store, &iter, column, toggle_item, -1);
877
878   /* do the same for the first child */
879   if(gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store),&next, &iter))
880     folder_toggle_recurse_tree(tree_store,&next,
881                                FOLDERCHECK_CHECK, toggle_item);
882
883   /* do the same for the next sibling */
884   if(gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &iter))
885     folder_toggle_recurse_tree(tree_store, &iter,
886                                FOLDERCHECK_CHECK, toggle_item);
887 }
888
889 /* Helper function to be used with a foreach statement of the model. Checks
890  * if a node is checked, and adds it to a list if it is. data us a (GSList**)
891  * where the result it to be stored */
892 static gboolean foldercheck_foreach_check(GtkTreeModel *model,
893                                           GtkTreePath *path,
894                                           GtkTreeIter *iter, gpointer data)
895 {
896   gboolean toggled_item;
897   GSList **list = (GSList**) data;
898
899   gtk_tree_model_get(model, iter, FOLDERCHECK_CHECK, &toggled_item, -1);
900
901   if(toggled_item) {
902     FolderItem *item;
903     gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
904     *list = g_slist_prepend(*list, item);
905   }
906
907   return FALSE;
908 }
909
910 /* Helper function to be used with a foreach statement of the model. Checks
911  * if a node is checked, and adds it to a list if it is. data us a (GSList**)
912  * where the result it to be stored */
913 static gboolean foldercheck_foreach_update_to_list(GtkTreeModel *model,
914                                                    GtkTreePath *path,
915                                                    GtkTreeIter *iter,
916                                                    gpointer data)
917 {
918   gchar *ident_tree, *ident_list;
919   FolderItem *item;
920   GSList *walk;
921   gboolean toggle_item = FALSE;
922   SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
923
924   gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
925
926   if(item->path != NULL)
927     ident_tree = folder_item_get_identifier(item);
928   else
929     return FALSE;
930
931   for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
932     FolderItem *list_item = (FolderItem*) walk->data;
933     ident_list = folder_item_get_identifier(list_item);
934     if(!strcmp2(ident_list,ident_tree)) {
935       toggle_item = TRUE;
936       g_free(ident_list);
937       break;
938     }
939     g_free(ident_list);
940   }
941   g_free(ident_tree);
942
943   gtk_tree_store_set(entry->tree_store, iter, FOLDERCHECK_CHECK,
944                      toggle_item, -1);
945
946   return FALSE;
947 }
948
949
950 /* Callback for the folder selection dialog. Basically a wrapper around 
951  * folder_checked that first resolves the name to an ID first. */
952 void notification_foldercheck_sel_folders_cb(GtkButton *button, gpointer data)
953 {
954   guint id;
955   gchar *name = (gchar*) data;
956
957   id = notification_register_folder_specific_list(name);
958
959   folder_checked(id);
960 }
961
962 static gboolean my_folder_update_hook(gpointer source, gpointer data)
963 {
964   FolderUpdateData *hookdata = (FolderUpdateData*) source;
965
966   if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) {
967     gint ii;
968     SpecificFolderArrayEntry *entry;
969     FolderItem *item = hookdata->item;
970
971     /* If that folder is in anywhere in the array, cut it out. */
972     for(ii = 0; ii < specific_folder_array_size; ii++) {
973       entry = foldercheck_get_entry_from_id(ii);
974       entry->list = g_slist_remove(entry->list, item);
975     } /* for all entries in the array */
976   } /* A FolderItem was deleted */
977
978   return FALSE;
979 }
980
981 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
982 {
983   if(event && (event->keyval == GDK_Escape)) {
984     foldercheck_cancel(NULL, data);
985     return TRUE;
986   }
987   return FALSE;
988 }