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