Improve some sieve-related translations
[claws.git] / src / plugins / notification / notification_foldercheck.c
blob8d6770709ae123dd0c7b03c8a012b1a004a235f6
2 /* Notification plugin for Claws Mail
3 * Copyright (C) 2005-2022 Holger Berndt and the Claws Mail Team
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.
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.
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/>.
19 /* This code is based on foldersel.c in Claws Mail.
20 * Some functions are only slightly modified, almost 1:1 copies from there. */
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 # include "claws-features.h"
25 #endif
27 #include <glib.h>
28 #include <glib/gi18n.h>
30 /* Basic definitions first */
31 #include "common/defs.h"
33 /* System includes */
34 #include <string.h>
35 #include <gdk/gdkkeysyms.h>
36 #include <gtk/gtk.h>
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"
49 /* local includes */
50 #include "notification_foldercheck.h"
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
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;
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;
81 static GArray *specific_folder_array;
82 static guint specific_folder_array_size;
84 static gulong hook_folder_update;
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
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);
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);
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)
129 SpecificFolderArrayEntry *entry;
130 gint ii = 0;
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;
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 == 0) {
143 debug_print("Warning: Failed to register hook to folder update "
144 "hooklist. "
145 "Strange things can occur when deleting folders.\n");
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(!g_strcmp0(entry->name,node_name))
154 return ii;
156 ii++;
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++;
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)
186 guint ii;
187 SpecificFolderArrayEntry *entry;
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);
200 if(specific_folder_array) {
201 /* Free array */
202 g_array_free(specific_folder_array, TRUE);
204 /* Unregister hook */
205 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_folder_update);
207 specific_folder_array = NULL;
208 specific_folder_array_size = 0;
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)
215 SpecificFolderArrayEntry *entry;
217 entry = foldercheck_get_entry_from_id(id);
218 if(entry) {
219 return entry->list;
221 else
222 return NULL;
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)
229 gchar *path;
230 XMLTag *tag;
231 XMLNode *xmlnode;
232 GNode *rootnode;
233 gint ii;
234 PrefFile *pfile;
236 /* Do nothing if foldercheck is not in use */
237 if(specific_folder_array_size == 0)
238 return;
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;
247 /* XML declarations */
248 xml_file_put_xml_decl(pfile->fp);
250 /* Build up XML tree */
252 /* root node */
253 tag = xml_tag_new("foldercheckarray");
254 xmlnode = xml_node_new(tag, NULL);
255 rootnode = g_node_new(xmlnode);
257 /* branch nodes */
258 for(ii = 0; ii < specific_folder_array_size; ii++) {
259 GNode *branchnode;
260 GSList *walk;
261 SpecificFolderArrayEntry *entry;
263 entry = foldercheck_get_entry_from_id(ii);
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);
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;
277 identifier = folder_item_get_identifier(item);
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 */
287 } /* for all branch nodes */
289 /* Actual writing and cleanup */
290 xml_write_tree(rootnode, pfile->fp);
292 if(prefs_file_close(pfile) < 0) {
293 debug_print("Notification plugin error: failed to write "
294 "file " FOLDERCHECK_ARRAY "\n");
297 /* Free XML tree */
298 xml_free_tree(rootnode);
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)
307 gchar *path;
308 GNode *rootnode, *node, *branchnode;
309 XMLNode *xmlnode;
310 gboolean success = FALSE;
312 path = foldercheck_get_array_path();
313 if(!is_file_exist(path)) {
314 path = NULL;
315 return FALSE;
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();
322 /* .. and evaluate the file */
323 rootnode = xml_parse_file(path);
324 path = NULL;
325 if(!rootnode)
326 return FALSE;
328 xmlnode = rootnode->data;
330 /* Check that root entry is "foldercheckarray" */
331 if(g_strcmp0(xmlnode->tag->tag, "foldercheckarray") != 0) {
332 g_warning("wrong foldercheck array file");
333 xml_free_tree(rootnode);
334 return FALSE;
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;
344 xmlnode = branchnode->data;
345 if(g_strcmp0(xmlnode->tag->tag, "branch") != 0) {
346 g_warning("tag name != \"branch\"");
347 return FALSE;
350 /* Attributes of the branch nodes */
351 list = xmlnode->tag->attr;
352 for(; list != NULL; list = list->next) {
353 XMLAttr *attr = list->data;
355 if(attr && attr->name && attr->value && !g_strcmp0(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;
363 if((list == NULL) || (entry == NULL)) {
364 g_warning("did not find attribute \"name\" in tag \"branch\"");
365 continue; /* with next branch */
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;
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");
377 /* Check if tag is "folderitem" */
378 xmlnode = node->data;
379 if(g_strcmp0(xmlnode->tag->tag, "folderitem") != 0) {
380 g_warning("tag name != \"folderitem\"");
381 continue; /* to next node in branch */
384 /* Attributes of the leaf nodes */
385 list = xmlnode->tag->attr;
386 for(; list != NULL; list = list->next) {
387 XMLAttr *attr = list->data;
389 if(attr && attr->name && attr->value &&
390 !g_strcmp0(attr->name, "identifier")) {
391 item = folder_find_item_from_identifier(attr->value);
392 break;
395 if((list == NULL) || (item == NULL)) {
396 g_warning("did not find attribute \"identifier\" in tag "
397 "\"folderitem\"");
398 continue; /* with next leaf node */
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);
406 } /* for all subnodes in branch */
408 } /* for all branches */
409 return success;
412 /* Stolen from folder.c. Return value should NOT be freed. */
413 static gchar *foldercheck_get_array_path(void)
415 static gchar *filename = NULL;
417 if(!filename)
418 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
419 FOLDERCHECK_ARRAY, NULL);
420 return filename;
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)
428 SpecificFolderArrayEntry *entry;
429 GSList *checked_list = NULL;
431 entry = foldercheck_get_entry_from_id(id);
433 /* Create window */
434 foldercheck_create_window(entry);
435 gtk_widget_show(entry->window);
436 manage_window_set_transient(GTK_WINDOW(entry->window));
438 entry->cancelled = entry->finished = FALSE;
439 while(entry->finished == FALSE)
440 gtk_main_iteration();
442 foldercheck_destroy_window(entry);
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);
449 if(entry->list) {
450 g_slist_free(entry->list);
451 entry->list = NULL;
453 entry->list = g_slist_copy(checked_list);
454 g_slist_free(checked_list);
457 gtk_tree_store_clear(entry->tree_store);
459 entry->cancelled = FALSE;
460 entry->finished = FALSE;
463 /* Create the window for selecting folders with checkboxes */
464 static void foldercheck_create_window(SpecificFolderArrayEntry *entry)
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;
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 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);
490 /* vbox */
491 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
492 gtk_container_add(GTK_CONTAINER(entry->window), vbox);
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);
502 /* pixbufs */
503 if(!folder_pixbuf)
504 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_CLOSE,
505 &folder_pixbuf);
506 if(!folderopen_pixbuf)
507 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_OPEN,
508 &folderopen_pixbuf);
509 if(!foldernoselect_pixbuf)
510 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_NOSELECT_CLOSE,
511 &foldernoselect_pixbuf);
512 if(!foldernoselectopen_pixbuf)
513 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_NOSELECT_OPEN,
514 &foldernoselectopen_pixbuf);
516 /* Tree store */
517 foldercheck_set_tree(entry);
518 gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
519 foldercheck_foreach_update_to_list, entry);
522 /* tree view */
523 entry->treeview =
524 gtk_tree_view_new_with_model(GTK_TREE_MODEL(entry->tree_store));
525 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(entry->treeview), FALSE);
526 gtk_tree_view_set_search_column(GTK_TREE_VIEW(entry->treeview),
527 FOLDERCHECK_FOLDERNAME);
528 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(entry->treeview),
529 prefs_common_get_prefs()->use_stripes_everywhere);
530 gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(entry->treeview), FALSE);
532 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(entry->treeview));
533 gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
534 gtk_tree_selection_set_select_function(selection, foldercheck_selected,
535 NULL, NULL);
537 gtk_container_add(GTK_CONTAINER(scrolledwin), entry->treeview);
539 /* --- column 1 --- */
540 column = gtk_tree_view_column_new();
541 gtk_tree_view_column_set_title(column, "sel");
542 gtk_tree_view_column_set_spacing(column, 2);
544 /* checkbox */
545 renderer = gtk_cell_renderer_toggle_new();
546 g_object_set(renderer, "xalign", 0.0, NULL);
547 gtk_tree_view_column_pack_start(column, renderer, TRUE);
548 g_signal_connect(renderer, "toggled", G_CALLBACK(folder_toggle_cb),entry);
549 gtk_tree_view_column_set_attributes(column, renderer,
550 "active", FOLDERCHECK_CHECK,NULL);
552 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
553 gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
555 /* --- column 2 --- */
556 column = gtk_tree_view_column_new();
557 gtk_tree_view_column_set_title(column, "Folder");
558 gtk_tree_view_column_set_spacing(column, 2);
560 /* pixbuf */
561 renderer = gtk_cell_renderer_pixbuf_new();
562 gtk_tree_view_column_pack_start(column, renderer, FALSE);
563 gtk_tree_view_column_set_attributes
564 (column, renderer,
565 "pixbuf", FOLDERCHECK_PIXBUF,
566 "pixbuf-expander-open", FOLDERCHECK_PIXBUF_OPEN,
567 "pixbuf-expander-closed", FOLDERCHECK_PIXBUF,
568 NULL);
570 /* text */
571 renderer = gtk_cell_renderer_text_new();
572 gtk_tree_view_column_pack_start(column, renderer, TRUE);
573 gtk_tree_view_column_set_attributes(column, renderer,
574 "text", FOLDERCHECK_FOLDERNAME,
575 NULL);
577 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
578 gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
580 /* recursive */
581 checkbox = gtk_check_button_new_with_label( _("select recursively"));
582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), FALSE);
583 g_signal_connect(G_OBJECT(checkbox), "toggled",
584 G_CALLBACK(foldercheck_recursive_cb), entry);
585 gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 10);
587 gtkut_stock_button_set_create(&confirm_area,
588 &cancel_button, NULL, _("_Cancel"),
589 &ok_button, NULL, _("_OK"),
590 NULL, NULL, NULL);
591 gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
592 gtk_widget_grab_default(ok_button);
594 g_signal_connect(G_OBJECT(ok_button), "clicked",
595 G_CALLBACK(foldercheck_ok), entry);
596 g_signal_connect(G_OBJECT(cancel_button), "clicked",
597 G_CALLBACK(foldercheck_cancel), entry);
599 if(!geometry.min_height) {
600 geometry.min_width = 360;
601 geometry.min_height = 360;
604 gtk_window_set_geometry_hints(GTK_WINDOW(entry->window), NULL, &geometry,
605 GDK_HINT_MIN_SIZE);
607 gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
609 gtk_widget_show_all(vbox);
612 static void foldercheck_destroy_window(SpecificFolderArrayEntry *entry)
614 gtk_widget_destroy(entry->window);
615 entry->window = NULL;
616 entry->treeview = NULL;
617 entry->recursive = FALSE;
620 /* Handler for the delete event of the windows for selecting folders */
621 static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data)
623 foldercheck_cancel(NULL, data);
624 return TRUE;
627 /* sortable_set_sort_func */
628 static gint foldercheck_folder_name_compare(GtkTreeModel *model,
629 GtkTreeIter *a, GtkTreeIter *b,
630 gpointer context)
632 gchar *str_a = NULL, *str_b = NULL;
633 gint val = 0;
634 FolderItem *item_a = NULL, *item_b = NULL;
635 GtkTreeIter parent;
637 gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERITEM, &item_a, -1);
638 gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERITEM, &item_b, -1);
640 /* no sort for root folder */
641 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, a))
642 return 0;
644 /* if both a and b are special folders, sort them according to
645 * their types (which is in-order). Note that this assumes that
646 * there are no multiple folders of a special type. */
647 if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL)
648 return item_a->stype - item_b->stype;
650 /* if b is normal folder, and a is not, b is smaller (ends up
651 * lower in the list) */
652 if (item_a->stype != F_NORMAL && item_b->stype == F_NORMAL)
653 return item_b->stype - item_a->stype;
655 /* if b is special folder, and a is not, b is larger (ends up
656 * higher in the list) */
657 if (item_a->stype == F_NORMAL && item_b->stype != F_NORMAL)
658 return item_b->stype - item_a->stype;
660 /* XXX g_utf8_collate_key() comparisons may speed things
661 * up when having large lists of folders */
662 gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERNAME, &str_a, -1);
663 gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERNAME, &str_b, -1);
665 /* otherwise just compare the folder names */
666 val = g_utf8_collate(str_a, str_b);
668 g_free(str_a);
669 g_free(str_b);
671 return val;
674 /* select_function of the gtk tree selection */
675 static gboolean foldercheck_selected(GtkTreeSelection *selection,
676 GtkTreeModel *model, GtkTreePath *path,
677 gboolean currently_selected,gpointer data)
679 GtkTreeIter iter;
680 FolderItem *item = NULL;
682 if (currently_selected)
683 return TRUE;
685 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
686 return TRUE;
688 gtk_tree_model_get(model, &iter, FOLDERCHECK_FOLDERITEM, &item, -1);
690 return TRUE;
693 /* Callback for the OK-button of the folderselection dialog */
694 static void foldercheck_ok(GtkButton *button, gpointer data)
696 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
698 entry->finished = TRUE;
701 /* Callback for the Cancel-button of the folderselection dialog. Gets also
702 * called on a delete-event of the folderselection window. */
703 static void foldercheck_cancel(GtkButton *button, gpointer data)
705 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
707 entry->cancelled = TRUE;
708 entry->finished = TRUE;
711 /* Set tree of the tree-store. This includes getting the folder tree and
712 * storing it */
713 static void foldercheck_set_tree(SpecificFolderArrayEntry *entry)
715 Folder *folder;
716 GList *list;
718 for(list = folder_get_list(); list != NULL; list = list->next) {
719 folder = FOLDER(list->data);
721 if(folder == NULL) {
722 debug_print("Notification plugin::foldercheck_set_tree(): Found a NULL folder.\n");
723 continue;
726 /* Only regard built-in folders, because folders from plugins (such as RSS, calendar,
727 * or plugin-provided mailbox storage systems like Maildir or MBox) may vanish
728 * without letting us know. */
729 switch(folder->klass->type) {
730 case F_MH:
731 case F_IMAP:
732 case F_NEWS:
733 foldercheck_insert_gnode_in_store(entry->tree_store, folder->node, NULL);
734 break;
735 default:
736 break;
740 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry->tree_store),
741 FOLDERCHECK_FOLDERNAME,
742 GTK_SORT_ASCENDING);
744 if(GTK_IS_TREE_VIEW(entry->treeview))
745 gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
748 /* Helper function for foldercheck_set_tree */
749 static void foldercheck_insert_gnode_in_store(GtkTreeStore *store, GNode *node,
750 GtkTreeIter *parent)
752 FolderItem *item;
753 GtkTreeIter child;
754 GNode *iter;
756 g_return_if_fail(node != NULL);
757 g_return_if_fail(node->data != NULL);
758 g_return_if_fail(store != NULL);
760 item = FOLDER_ITEM(node->data);
761 foldercheck_append_item(store, item, &child, parent);
763 /* insert its children (this node as parent) */
764 for(iter = node->children; iter != NULL; iter = iter->next)
765 foldercheck_insert_gnode_in_store(store, iter, &child);
768 /* Helper function for foldercheck_insert_gnode_in_store */
769 static void foldercheck_append_item(GtkTreeStore *store, FolderItem *item,
770 GtkTreeIter *iter, GtkTreeIter *parent)
772 gchar *name, *tmpname;
773 GdkPixbuf *pixbuf, *pixbuf_open;
775 name = tmpname = folder_item_get_name(item);
777 if (item->stype != F_NORMAL && FOLDER_IS_LOCAL(item->folder)) {
778 switch (item->stype) {
779 case F_INBOX:
780 if (!g_strcmp0(item->name, INBOX_DIR))
781 name = "Inbox";
782 break;
783 case F_OUTBOX:
784 if (!g_strcmp0(item->name, OUTBOX_DIR))
785 name = "Sent";
786 break;
787 case F_QUEUE:
788 if (!g_strcmp0(item->name, QUEUE_DIR))
789 name = "Queue";
790 break;
791 case F_TRASH:
792 if (!g_strcmp0(item->name, TRASH_DIR))
793 name = "Trash";
794 break;
795 case F_DRAFT:
796 if (!g_strcmp0(item->name, DRAFT_DIR))
797 name = "Drafts";
798 break;
799 default:
800 break;
804 if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0) {
805 name = g_strdup_printf("%s (%d)", name, item->total_msgs);
806 } else if (item->unread_msgs > 0) {
807 name = g_strdup_printf("%s (%d)", name, item->unread_msgs);
808 } else
809 name = g_strdup(name);
811 pixbuf = item->no_select ? foldernoselect_pixbuf : folder_pixbuf;
812 pixbuf_open =
813 item->no_select ? foldernoselectopen_pixbuf : folderopen_pixbuf;
815 /* insert this node */
816 gtk_tree_store_append(store, iter, parent);
817 gtk_tree_store_set(store, iter,
818 FOLDERCHECK_FOLDERNAME, name,
819 FOLDERCHECK_FOLDERITEM, item,
820 FOLDERCHECK_PIXBUF, pixbuf,
821 FOLDERCHECK_PIXBUF_OPEN, pixbuf_open,
822 -1);
824 g_free(tmpname);
827 /* Callback of the recursive-checkbox */
828 static void foldercheck_recursive_cb(GtkToggleButton *button, gpointer data)
830 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
832 entry->recursive = gtk_toggle_button_get_active(button);
835 /* Callback of the checkboxes corresponding to the folders. Obeys
836 * the "recursive" selection. */
837 static void folder_toggle_cb(GtkCellRendererToggle *cell_renderer,
838 gchar *path_str, gpointer data)
840 gboolean toggle_item;
841 GtkTreeIter iter;
842 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
843 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
845 gtk_tree_model_get_iter(GTK_TREE_MODEL(entry->tree_store), &iter, path);
846 gtk_tree_path_free(path);
847 gtk_tree_model_get(GTK_TREE_MODEL(entry->tree_store), &iter,
848 FOLDERCHECK_CHECK, &toggle_item, -1);
849 toggle_item = !toggle_item;
851 if(!entry->recursive)
852 gtk_tree_store_set(entry->tree_store, &iter,
853 FOLDERCHECK_CHECK, toggle_item, -1);
854 else {
855 GtkTreeIter child;
856 gtk_tree_store_set(entry->tree_store, &iter,
857 FOLDERCHECK_CHECK, toggle_item, -1);
858 if(gtk_tree_model_iter_children(GTK_TREE_MODEL(entry->tree_store),
859 &child, &iter))
860 folder_toggle_recurse_tree(entry->tree_store,&child,
861 FOLDERCHECK_CHECK,toggle_item);
864 while(gtk_events_pending())
865 gtk_main_iteration();
868 /* Helper function for folder_toggle_cb */
869 /* This function calls itself recurively */
870 static void folder_toggle_recurse_tree(GtkTreeStore *tree_store,
871 GtkTreeIter *iterp, gint column,
872 gboolean toggle_item)
874 GtkTreeIter iter = *iterp;
875 GtkTreeIter next;
877 /* set the value of this iter */
878 gtk_tree_store_set(tree_store, &iter, column, toggle_item, -1);
880 /* do the same for the first child */
881 if(gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store),&next, &iter))
882 folder_toggle_recurse_tree(tree_store,&next,
883 FOLDERCHECK_CHECK, toggle_item);
885 /* do the same for the next sibling */
886 if(gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &iter))
887 folder_toggle_recurse_tree(tree_store, &iter,
888 FOLDERCHECK_CHECK, toggle_item);
891 /* Helper function to be used with a foreach statement of the model. Checks
892 * if a node is checked, and adds it to a list if it is. data us a (GSList**)
893 * where the result it to be stored */
894 static gboolean foldercheck_foreach_check(GtkTreeModel *model,
895 GtkTreePath *path,
896 GtkTreeIter *iter, gpointer data)
898 gboolean toggled_item;
899 GSList **list = (GSList**) data;
901 gtk_tree_model_get(model, iter, FOLDERCHECK_CHECK, &toggled_item, -1);
903 if(toggled_item) {
904 FolderItem *item;
905 gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
906 *list = g_slist_prepend(*list, item);
909 return FALSE;
912 /* Helper function to be used with a foreach statement of the model. Checks
913 * if a node is checked, and adds it to a list if it is. data us a (GSList**)
914 * where the result it to be stored */
915 static gboolean foldercheck_foreach_update_to_list(GtkTreeModel *model,
916 GtkTreePath *path,
917 GtkTreeIter *iter,
918 gpointer data)
920 gchar *ident_tree, *ident_list;
921 FolderItem *item;
922 GSList *walk;
923 gboolean toggle_item = FALSE;
924 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
926 gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
928 if(item->path != NULL)
929 ident_tree = folder_item_get_identifier(item);
930 else
931 return FALSE;
933 for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
934 FolderItem *list_item = (FolderItem*) walk->data;
935 ident_list = folder_item_get_identifier(list_item);
936 if(!g_strcmp0(ident_list,ident_tree)) {
937 toggle_item = TRUE;
938 g_free(ident_list);
939 break;
941 g_free(ident_list);
943 g_free(ident_tree);
945 gtk_tree_store_set(entry->tree_store, iter, FOLDERCHECK_CHECK,
946 toggle_item, -1);
948 return FALSE;
952 /* Callback for the folder selection dialog. Basically a wrapper around
953 * folder_checked that first resolves the name to an ID first. */
954 void notification_foldercheck_sel_folders_cb(GtkButton *button, gpointer data)
956 guint id;
957 gchar *name = (gchar*) data;
959 id = notification_register_folder_specific_list(name);
961 folder_checked(id);
964 static gboolean my_folder_update_hook(gpointer source, gpointer data)
966 FolderUpdateData *hookdata = (FolderUpdateData*) source;
968 if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) {
969 gint ii;
970 SpecificFolderArrayEntry *entry;
971 FolderItem *item = hookdata->item;
973 /* If that folder is in anywhere in the array, cut it out. */
974 for(ii = 0; ii < specific_folder_array_size; ii++) {
975 entry = foldercheck_get_entry_from_id(ii);
976 entry->list = g_slist_remove(entry->list, item);
977 } /* for all entries in the array */
978 } /* A FolderItem was deleted */
980 return FALSE;
983 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
985 if(event && (event->keyval == GDK_KEY_Escape)) {
986 foldercheck_cancel(NULL, data);
987 return TRUE;
989 return FALSE;