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