add support for Ayatana indicator to Notification plugin
[claws.git] / src / plugins / notification / notification_foldercheck.c
blob133756958043fe986778636e2c21cf8730006163
2 /* Notification plugin for Claws Mail
3 * Copyright (C) 2005-2024 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"
48 /* local includes */
49 #include "notification_foldercheck.h"
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
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;
74 /* variables with file scope */
75 static GdkPixbuf *folder_pixbuf;
76 static GdkPixbuf *folderopen_pixbuf;
77 static GdkPixbuf *foldernoselect_pixbuf;
78 static GdkPixbuf *foldernoselectopen_pixbuf;
80 static GArray *specific_folder_array;
81 static guint specific_folder_array_size;
83 static gulong hook_folder_update;
86 /* defines */
87 #define FOLDERCHECK_ARRAY "notification_foldercheck.xml"
88 #define foldercheck_get_entry_from_id(id) \
89 ((id) < specific_folder_array_size) ? \
90 g_array_index(specific_folder_array,SpecificFolderArrayEntry*,(id)) : NULL
92 /* function prototypes */
93 static void folder_checked(guint);
94 static void foldercheck_create_window(SpecificFolderArrayEntry*);
95 static void foldercheck_destroy_window(SpecificFolderArrayEntry*);
96 static gint foldercheck_folder_name_compare(GtkTreeModel*, GtkTreeIter*,
97 GtkTreeIter*, gpointer);
98 static gboolean foldercheck_selected(GtkTreeSelection*,
99 GtkTreeModel*, GtkTreePath*,
100 gboolean, gpointer);
102 static gint delete_event(GtkWidget*, GdkEventAny*, gpointer);
103 static void foldercheck_ok(GtkButton*, gpointer);
104 static void foldercheck_cancel(GtkButton*, gpointer);
105 static void foldercheck_set_tree(SpecificFolderArrayEntry*);
106 static void foldercheck_insert_gnode_in_store(GtkTreeStore*, GNode*,
107 GtkTreeIter*);
108 static void foldercheck_append_item(GtkTreeStore*, FolderItem*,
109 GtkTreeIter*, GtkTreeIter*);
110 static void foldercheck_recursive_cb(GtkToggleButton*, gpointer);
111 static void folder_toggle_cb(GtkCellRendererToggle*, gchar*, gpointer);
112 static void folder_toggle_recurse_tree(GtkTreeStore*, GtkTreeIter*, gint,
113 gboolean);
114 static gboolean foldercheck_foreach_check(GtkTreeModel*, GtkTreePath*,
115 GtkTreeIter*, gpointer);
116 static gboolean foldercheck_foreach_update_to_list(GtkTreeModel*, GtkTreePath*,
117 GtkTreeIter*, gpointer);
118 static gchar *foldercheck_get_array_path(void);
119 static gboolean my_folder_update_hook(gpointer, gpointer);
120 static gboolean key_pressed(GtkWidget*, GdkEventKey*,gpointer);
123 /* Creates an entry in the specific_folder_array, and fills it with a new
124 * SpecificFolderArrayEntry*. If specific_folder_array already has an entry
125 * with the same name, return its ID. (The ID is the index in the array.) */
126 guint notification_register_folder_specific_list(gchar *node_name)
128 SpecificFolderArrayEntry *entry;
129 gint ii = 0;
131 /* If array does not yet exist, create it. */
132 if(!specific_folder_array) {
133 specific_folder_array = g_array_new(FALSE, FALSE,
134 sizeof(SpecificFolderArrayEntry*));
135 specific_folder_array_size = 0;
137 /* Register hook for folder update */
138 /* "The hook is registered" is bound to "the array is allocated" */
139 hook_folder_update = hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
140 my_folder_update_hook, NULL);
141 if(hook_folder_update == 0) {
142 debug_print("Warning: Failed to register hook to folder update "
143 "hooklist. "
144 "Strange things can occur when deleting folders.\n");
148 /* Check if we already have such a name. If so, return its id. */
149 while(ii < specific_folder_array_size) {
150 entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
151 if(entry) {
152 if(!g_strcmp0(entry->name,node_name))
153 return ii;
155 ii++;
158 /* Create an entry with the corresponding node name. */
159 entry = g_new(SpecificFolderArrayEntry, 1);
160 entry->name = g_strdup(node_name);
161 entry->list = NULL;
162 entry->window = NULL;
163 entry->treeview = NULL;
164 entry->cancelled = FALSE;
165 entry->finished = FALSE;
166 entry->recursive = FALSE;
167 entry->tree_store = gtk_tree_store_new(N_FOLDERCHECK_COLUMNS,
168 G_TYPE_STRING,
169 G_TYPE_POINTER,
170 GDK_TYPE_PIXBUF,
171 GDK_TYPE_PIXBUF,
172 G_TYPE_BOOLEAN);
173 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(entry->tree_store),
174 FOLDERCHECK_FOLDERNAME,
175 foldercheck_folder_name_compare,
176 NULL, NULL);
177 specific_folder_array = g_array_append_val(specific_folder_array, entry);
178 return specific_folder_array_size++;
181 /* This function is called in plugin_done. It frees the whole
182 * folder_specific_array with all its entries. */
183 void notification_free_folder_specific_array(void)
185 guint ii;
186 SpecificFolderArrayEntry *entry;
188 for(ii = 0; ii < specific_folder_array_size; ii++) {
189 entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
190 if(entry) {
191 g_free(entry->name);
192 if(entry->list)
193 g_slist_free(entry->list);
194 if(entry->tree_store)
195 g_object_unref(G_OBJECT(entry->tree_store));
196 g_free(entry);
199 if(specific_folder_array) {
200 /* Free array */
201 g_array_free(specific_folder_array, TRUE);
203 /* Unregister hook */
204 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_folder_update);
206 specific_folder_array = NULL;
207 specific_folder_array_size = 0;
210 /* Returns the list of the entry with the corresponding ID, or NULL if
211 * no such element exists. */
212 GSList* notification_foldercheck_get_list(guint id)
214 SpecificFolderArrayEntry *entry;
216 entry = foldercheck_get_entry_from_id(id);
217 if(entry) {
218 return entry->list;
220 else
221 return NULL;
224 /* Save selections in a common xml-file. Called when unloading the plugin.
225 * This is analog to folder.h::folder_write_list. */
226 void notification_foldercheck_write_array(void)
228 gchar *path;
229 XMLTag *tag;
230 XMLNode *xmlnode;
231 GNode *rootnode;
232 gint ii;
233 PrefFile *pfile;
235 /* Do nothing if foldercheck is not in use */
236 if(specific_folder_array_size == 0)
237 return;
239 path = foldercheck_get_array_path();
240 if((pfile = prefs_write_open(path)) == NULL) {
241 debug_print("Notification plugin error: cannot open "
242 "file " FOLDERCHECK_ARRAY " for writing\n");
243 return;
246 /* XML declarations */
247 xml_file_put_xml_decl(pfile->fp);
249 /* Build up XML tree */
251 /* root node */
252 tag = xml_tag_new("foldercheckarray");
253 xmlnode = xml_node_new(tag, NULL);
254 rootnode = g_node_new(xmlnode);
256 /* branch nodes */
257 for(ii = 0; ii < specific_folder_array_size; ii++) {
258 GNode *branchnode;
259 GSList *walk;
260 SpecificFolderArrayEntry *entry;
262 entry = foldercheck_get_entry_from_id(ii);
264 tag = xml_tag_new("branch");
265 xml_tag_add_attr(tag, xml_attr_new("name",entry->name));
266 xmlnode = xml_node_new(tag, NULL);
267 branchnode = g_node_new(xmlnode);
268 g_node_append(rootnode, branchnode);
270 /* Write out the list as leaf nodes */
271 for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
272 gchar *identifier;
273 GNode *node;
274 FolderItem *item = (FolderItem*) walk->data;
276 identifier = folder_item_get_identifier(item);
278 tag = xml_tag_new("folderitem");
279 xml_tag_add_attr(tag, xml_attr_new("identifier", identifier));
280 g_free(identifier);
281 xmlnode = xml_node_new(tag, NULL);
282 node = g_node_new(xmlnode);
283 g_node_append(branchnode, node);
284 } /* for all list elements in branch node */
286 } /* for all branch nodes */
288 /* Actual writing and cleanup */
289 xml_write_tree(rootnode, pfile->fp);
291 if(prefs_file_close(pfile) < 0) {
292 debug_print("Notification plugin error: failed to write "
293 "file " FOLDERCHECK_ARRAY "\n");
296 /* Free XML tree */
297 xml_free_tree(rootnode);
300 /* Read selections from a common xml-file. Called when loading the plugin.
301 * Returns TRUE if data has been read, FALSE if no data is available
302 * or an error occurred.
303 * This is analog to folder.h::folder_read_list. */
304 gboolean notification_foldercheck_read_array(void)
306 gchar *path;
307 GNode *rootnode, *node, *branchnode;
308 XMLNode *xmlnode;
309 gboolean success = FALSE;
311 path = foldercheck_get_array_path();
312 if(!is_file_exist(path)) {
313 path = NULL;
314 return FALSE;
317 /* We don't do merging, so if the file existed, clear what we
318 have stored in memory right now.. */
319 notification_free_folder_specific_array();
321 /* .. and evaluate the file */
322 rootnode = xml_parse_file(path);
323 path = NULL;
324 if(!rootnode)
325 return FALSE;
327 xmlnode = rootnode->data;
329 /* Check that root entry is "foldercheckarray" */
330 if(g_strcmp0(xmlnode->tag->tag, "foldercheckarray") != 0) {
331 g_warning("wrong foldercheck array file");
332 xml_free_tree(rootnode);
333 return FALSE;
336 /* Process branch entries */
337 for(branchnode = rootnode->children; branchnode != NULL;
338 branchnode = branchnode->next) {
339 GList *list;
340 guint id;
341 SpecificFolderArrayEntry *entry = NULL;
343 xmlnode = branchnode->data;
344 if(g_strcmp0(xmlnode->tag->tag, "branch") != 0) {
345 g_warning("tag name != \"branch\"");
346 return FALSE;
349 /* Attributes of the branch nodes */
350 list = xmlnode->tag->attr;
351 for(; list != NULL; list = list->next) {
352 XMLAttr *attr = list->data;
354 if(attr && attr->name && attr->value && !g_strcmp0(attr->name, "name")) {
355 id = notification_register_folder_specific_list(attr->value);
356 entry = foldercheck_get_entry_from_id(id);
357 /* We have found something */
358 success = TRUE;
359 break;
362 if((list == NULL) || (entry == NULL)) {
363 g_warning("did not find attribute \"name\" in tag \"branch\"");
364 continue; /* with next branch */
367 /* Now descent into the children of the brach, which are the folderitems */
368 for(node = branchnode->children; node != NULL; node = node->next) {
369 FolderItem *item = NULL;
371 /* These should all be leaves. */
372 if(!G_NODE_IS_LEAF(node))
373 g_warning("subnodes in \"branch\" nodes should all be leaves, "
374 "ignoring deeper subnodes");
376 /* Check if tag is "folderitem" */
377 xmlnode = node->data;
378 if(g_strcmp0(xmlnode->tag->tag, "folderitem") != 0) {
379 g_warning("tag name != \"folderitem\"");
380 continue; /* to next node in branch */
383 /* Attributes of the leaf nodes */
384 list = xmlnode->tag->attr;
385 for(; list != NULL; list = list->next) {
386 XMLAttr *attr = list->data;
388 if(attr && attr->name && attr->value &&
389 !g_strcmp0(attr->name, "identifier")) {
390 item = folder_find_item_from_identifier(attr->value);
391 break;
394 if((list == NULL) || (item == NULL)) {
395 g_warning("did not find attribute \"identifier\" in tag "
396 "\"folderitem\"");
397 continue; /* with next leaf node */
400 /* Store all FolderItems in the list */
401 /* We started with a cleared array, so we don't need to check if
402 it's already in there. */
403 entry->list = g_slist_prepend(entry->list, item);
405 } /* for all subnodes in branch */
407 } /* for all branches */
408 return success;
411 /* Stolen from folder.c. Return value should NOT be freed. */
412 static gchar *foldercheck_get_array_path(void)
414 static gchar *filename = NULL;
416 if(!filename)
417 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
418 FOLDERCHECK_ARRAY, NULL);
419 return filename;
422 /* Callback for selecting folders. If no selection dialog exists yet, create
423 * one and initialize the selection. The recurse through the whole model, and
424 * add all selected items to the list. */
425 static void folder_checked(guint id)
427 SpecificFolderArrayEntry *entry;
428 GSList *checked_list = NULL;
430 entry = foldercheck_get_entry_from_id(id);
432 /* Create window */
433 foldercheck_create_window(entry);
434 gtk_widget_show(entry->window);
435 manage_window_set_transient(GTK_WINDOW(entry->window));
437 entry->cancelled = entry->finished = FALSE;
438 while(entry->finished == FALSE)
439 gtk_main_iteration();
441 foldercheck_destroy_window(entry);
443 if(!entry->cancelled) {
444 /* recurse through the whole model, add all selected items to the list */
445 gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
446 foldercheck_foreach_check, &checked_list);
448 if(entry->list) {
449 g_slist_free(entry->list);
450 entry->list = NULL;
452 entry->list = g_slist_copy(checked_list);
453 g_slist_free(checked_list);
456 gtk_tree_store_clear(entry->tree_store);
458 entry->cancelled = FALSE;
459 entry->finished = FALSE;
462 /* Create the window for selecting folders with checkboxes */
463 static void foldercheck_create_window(SpecificFolderArrayEntry *entry)
465 GtkWidget *vbox;
466 GtkWidget *scrolledwin;
467 GtkWidget *confirm_area;
468 GtkWidget *checkbox;
469 GtkWidget *cancel_button;
470 GtkWidget *ok_button;
471 GtkTreeSelection *selection;
472 GtkTreeViewColumn *column;
473 GtkCellRenderer *renderer;
474 static GdkGeometry geometry;
476 /* Create window */
477 entry->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_foldercheck");
478 gtk_window_set_title(GTK_WINDOW(entry->window), _("Select folder(s)"));
479 gtk_container_set_border_width(GTK_CONTAINER(entry->window), 4);
480 gtk_window_set_position(GTK_WINDOW(entry->window), GTK_WIN_POS_CENTER);
481 gtk_window_set_modal(GTK_WINDOW(entry->window), TRUE);
482 gtk_window_set_resizable(GTK_WINDOW(entry->window), TRUE);
483 g_signal_connect(G_OBJECT(entry->window), "delete_event",
484 G_CALLBACK(delete_event), entry);
485 g_signal_connect(G_OBJECT(entry->window), "key_press_event",
486 G_CALLBACK(key_pressed), entry);
487 MANAGE_WINDOW_SIGNALS_CONNECT(entry->window);
489 /* vbox */
490 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
491 gtk_container_add(GTK_CONTAINER(entry->window), vbox);
493 /* scrolled window */
494 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
495 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
496 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
497 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
498 GTK_SHADOW_IN);
499 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
501 /* pixbufs */
502 if(!folder_pixbuf)
503 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_CLOSE,
504 &folder_pixbuf);
505 if(!folderopen_pixbuf)
506 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_OPEN,
507 &folderopen_pixbuf);
508 if(!foldernoselect_pixbuf)
509 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_NOSELECT_CLOSE,
510 &foldernoselect_pixbuf);
511 if(!foldernoselectopen_pixbuf)
512 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_NOSELECT_OPEN,
513 &foldernoselectopen_pixbuf);
515 /* Tree store */
516 foldercheck_set_tree(entry);
517 gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
518 foldercheck_foreach_update_to_list, entry);
521 /* tree view */
522 entry->treeview =
523 gtk_tree_view_new_with_model(GTK_TREE_MODEL(entry->tree_store));
524 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(entry->treeview), FALSE);
525 gtk_tree_view_set_search_column(GTK_TREE_VIEW(entry->treeview),
526 FOLDERCHECK_FOLDERNAME);
527 gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(entry->treeview), FALSE);
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);
534 gtk_container_add(GTK_CONTAINER(scrolledwin), entry->treeview);
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);
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);
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);
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);
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);
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);
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);
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);
584 gtkut_stock_button_set_create(&confirm_area,
585 &cancel_button, NULL, _("_Cancel"),
586 &ok_button, NULL, _("_OK"),
587 NULL, NULL, NULL);
588 gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
589 gtk_widget_grab_default(ok_button);
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);
596 if(!geometry.min_height) {
597 geometry.min_width = 360;
598 geometry.min_height = 360;
601 gtk_window_set_geometry_hints(GTK_WINDOW(entry->window), NULL, &geometry,
602 GDK_HINT_MIN_SIZE);
604 gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
606 gtk_widget_show_all(vbox);
609 static void foldercheck_destroy_window(SpecificFolderArrayEntry *entry)
611 gtk_widget_destroy(entry->window);
612 entry->window = NULL;
613 entry->treeview = NULL;
614 entry->recursive = FALSE;
617 /* Handler for the delete event of the windows for selecting folders */
618 static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data)
620 foldercheck_cancel(NULL, data);
621 return TRUE;
624 /* sortable_set_sort_func */
625 static gint foldercheck_folder_name_compare(GtkTreeModel *model,
626 GtkTreeIter *a, GtkTreeIter *b,
627 gpointer context)
629 gchar *str_a = NULL, *str_b = NULL;
630 gint val = 0;
631 FolderItem *item_a = NULL, *item_b = NULL;
632 GtkTreeIter parent;
634 gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERITEM, &item_a, -1);
635 gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERITEM, &item_b, -1);
637 /* no sort for root folder */
638 if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, a))
639 return 0;
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;
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;
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;
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);
662 /* otherwise just compare the folder names */
663 val = g_utf8_collate(str_a, str_b);
665 g_free(str_a);
666 g_free(str_b);
668 return val;
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)
676 GtkTreeIter iter;
677 FolderItem *item = NULL;
679 if (currently_selected)
680 return TRUE;
682 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
683 return TRUE;
685 gtk_tree_model_get(model, &iter, FOLDERCHECK_FOLDERITEM, &item, -1);
687 return TRUE;
690 /* Callback for the OK-button of the folderselection dialog */
691 static void foldercheck_ok(GtkButton *button, gpointer data)
693 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
695 entry->finished = TRUE;
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)
702 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
704 entry->cancelled = TRUE;
705 entry->finished = TRUE;
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)
712 Folder *folder;
713 GList *list;
715 for(list = folder_get_list(); list != NULL; list = list->next) {
716 folder = FOLDER(list->data);
718 if(folder == NULL) {
719 debug_print("Notification plugin::foldercheck_set_tree(): Found a NULL folder.\n");
720 continue;
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;
737 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry->tree_store),
738 FOLDERCHECK_FOLDERNAME,
739 GTK_SORT_ASCENDING);
741 if(GTK_IS_TREE_VIEW(entry->treeview))
742 gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
745 /* Helper function for foldercheck_set_tree */
746 static void foldercheck_insert_gnode_in_store(GtkTreeStore *store, GNode *node,
747 GtkTreeIter *parent)
749 FolderItem *item;
750 GtkTreeIter child;
751 GNode *iter;
753 g_return_if_fail(node != NULL);
754 g_return_if_fail(node->data != NULL);
755 g_return_if_fail(store != NULL);
757 item = FOLDER_ITEM(node->data);
758 foldercheck_append_item(store, item, &child, parent);
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);
765 /* Helper function for foldercheck_insert_gnode_in_store */
766 static void foldercheck_append_item(GtkTreeStore *store, FolderItem *item,
767 GtkTreeIter *iter, GtkTreeIter *parent)
769 gchar *name, *tmpname;
770 GdkPixbuf *pixbuf, *pixbuf_open;
772 name = tmpname = folder_item_get_name(item);
774 if (item->stype != F_NORMAL && FOLDER_IS_LOCAL(item->folder)) {
775 switch (item->stype) {
776 case F_INBOX:
777 if (!g_strcmp0(item->name, INBOX_DIR))
778 name = "Inbox";
779 break;
780 case F_OUTBOX:
781 if (!g_strcmp0(item->name, OUTBOX_DIR))
782 name = "Sent";
783 break;
784 case F_QUEUE:
785 if (!g_strcmp0(item->name, QUEUE_DIR))
786 name = "Queue";
787 break;
788 case F_TRASH:
789 if (!g_strcmp0(item->name, TRASH_DIR))
790 name = "Trash";
791 break;
792 case F_DRAFT:
793 if (!g_strcmp0(item->name, DRAFT_DIR))
794 name = "Drafts";
795 break;
796 default:
797 break;
801 if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0) {
802 name = g_strdup_printf("%s (%d)", name, item->total_msgs);
803 } else if (item->unread_msgs > 0) {
804 name = g_strdup_printf("%s (%d)", name, item->unread_msgs);
805 } else
806 name = g_strdup(name);
808 pixbuf = item->no_select ? foldernoselect_pixbuf : folder_pixbuf;
809 pixbuf_open =
810 item->no_select ? foldernoselectopen_pixbuf : folderopen_pixbuf;
812 /* insert this node */
813 gtk_tree_store_append(store, iter, parent);
814 gtk_tree_store_set(store, iter,
815 FOLDERCHECK_FOLDERNAME, name,
816 FOLDERCHECK_FOLDERITEM, item,
817 FOLDERCHECK_PIXBUF, pixbuf,
818 FOLDERCHECK_PIXBUF_OPEN, pixbuf_open,
819 -1);
821 g_free(tmpname);
824 /* Callback of the recursive-checkbox */
825 static void foldercheck_recursive_cb(GtkToggleButton *button, gpointer data)
827 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
829 entry->recursive = gtk_toggle_button_get_active(button);
832 /* Callback of the checkboxes corresponding to the folders. Obeys
833 * the "recursive" selection. */
834 static void folder_toggle_cb(GtkCellRendererToggle *cell_renderer,
835 gchar *path_str, gpointer data)
837 gboolean toggle_item;
838 GtkTreeIter iter;
839 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
840 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
842 gtk_tree_model_get_iter(GTK_TREE_MODEL(entry->tree_store), &iter, path);
843 gtk_tree_path_free(path);
844 gtk_tree_model_get(GTK_TREE_MODEL(entry->tree_store), &iter,
845 FOLDERCHECK_CHECK, &toggle_item, -1);
846 toggle_item = !toggle_item;
848 if(!entry->recursive)
849 gtk_tree_store_set(entry->tree_store, &iter,
850 FOLDERCHECK_CHECK, toggle_item, -1);
851 else {
852 GtkTreeIter child;
853 gtk_tree_store_set(entry->tree_store, &iter,
854 FOLDERCHECK_CHECK, toggle_item, -1);
855 if(gtk_tree_model_iter_children(GTK_TREE_MODEL(entry->tree_store),
856 &child, &iter))
857 folder_toggle_recurse_tree(entry->tree_store,&child,
858 FOLDERCHECK_CHECK,toggle_item);
861 while(gtk_events_pending())
862 gtk_main_iteration();
865 /* Helper function for folder_toggle_cb */
866 /* This function calls itself recurively */
867 static void folder_toggle_recurse_tree(GtkTreeStore *tree_store,
868 GtkTreeIter *iterp, gint column,
869 gboolean toggle_item)
871 GtkTreeIter iter = *iterp;
872 GtkTreeIter next;
874 /* set the value of this iter */
875 gtk_tree_store_set(tree_store, &iter, column, toggle_item, -1);
877 /* do the same for the first child */
878 if(gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store),&next, &iter))
879 folder_toggle_recurse_tree(tree_store,&next,
880 FOLDERCHECK_CHECK, toggle_item);
882 /* do the same for the next sibling */
883 if(gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &iter))
884 folder_toggle_recurse_tree(tree_store, &iter,
885 FOLDERCHECK_CHECK, toggle_item);
888 /* Helper function to be used with a foreach statement of the model. Checks
889 * if a node is checked, and adds it to a list if it is. data us a (GSList**)
890 * where the result it to be stored */
891 static gboolean foldercheck_foreach_check(GtkTreeModel *model,
892 GtkTreePath *path,
893 GtkTreeIter *iter, gpointer data)
895 gboolean toggled_item;
896 GSList **list = (GSList**) data;
898 gtk_tree_model_get(model, iter, FOLDERCHECK_CHECK, &toggled_item, -1);
900 if(toggled_item) {
901 FolderItem *item;
902 gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
903 *list = g_slist_prepend(*list, item);
906 return FALSE;
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_update_to_list(GtkTreeModel *model,
913 GtkTreePath *path,
914 GtkTreeIter *iter,
915 gpointer data)
917 gchar *ident_tree, *ident_list;
918 FolderItem *item;
919 GSList *walk;
920 gboolean toggle_item = FALSE;
921 SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
923 gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
925 if(item->path != NULL)
926 ident_tree = folder_item_get_identifier(item);
927 else
928 return FALSE;
930 for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
931 FolderItem *list_item = (FolderItem*) walk->data;
932 ident_list = folder_item_get_identifier(list_item);
933 if(!g_strcmp0(ident_list,ident_tree)) {
934 toggle_item = TRUE;
935 g_free(ident_list);
936 break;
938 g_free(ident_list);
940 g_free(ident_tree);
942 gtk_tree_store_set(entry->tree_store, iter, FOLDERCHECK_CHECK,
943 toggle_item, -1);
945 return FALSE;
949 /* Callback for the folder selection dialog. Basically a wrapper around
950 * folder_checked that first resolves the name to an ID first. */
951 void notification_foldercheck_sel_folders_cb(GtkButton *button, gpointer data)
953 guint id;
954 gchar *name = (gchar*) data;
956 id = notification_register_folder_specific_list(name);
958 folder_checked(id);
961 static gboolean my_folder_update_hook(gpointer source, gpointer data)
963 FolderUpdateData *hookdata = (FolderUpdateData*) source;
965 if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) {
966 gint ii;
967 SpecificFolderArrayEntry *entry;
968 FolderItem *item = hookdata->item;
970 /* If that folder is in anywhere in the array, cut it out. */
971 for(ii = 0; ii < specific_folder_array_size; ii++) {
972 entry = foldercheck_get_entry_from_id(ii);
973 entry->list = g_slist_remove(entry->list, item);
974 } /* for all entries in the array */
975 } /* A FolderItem was deleted */
977 return FALSE;
980 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
982 if(event && (event->keyval == GDK_KEY_Escape)) {
983 foldercheck_cancel(NULL, data);
984 return TRUE;
986 return FALSE;