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. */
24 # include "claws-features.h"
28 #include <glib/gi18n.h>
30 /* Basic definitions first */
31 #include "common/defs.h"
35 #include <gdk/gdkkeysyms.h>
38 /* Claws Mail includes */
39 #include "manage_window.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"
50 #include "notification_foldercheck.h"
52 /* enums and structures */
54 FOLDERCHECK_FOLDERNAME
,
55 FOLDERCHECK_FOLDERITEM
,
57 FOLDERCHECK_PIXBUF_OPEN
,
66 GtkTreeStore
*tree_store
;
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
;
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
*,
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
*,
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
,
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
;
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 "
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
);
153 if(!g_strcmp0(entry
->name
,node_name
))
159 /* Create an entry with the corresponding node name. */
160 entry
= g_new(SpecificFolderArrayEntry
, 1);
161 entry
->name
= g_strdup(node_name
);
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
,
174 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(entry
->tree_store
),
175 FOLDERCHECK_FOLDERNAME
,
176 foldercheck_folder_name_compare
,
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)
187 SpecificFolderArrayEntry
*entry
;
189 for(ii
= 0; ii
< specific_folder_array_size
; ii
++) {
190 entry
= g_array_index(specific_folder_array
,SpecificFolderArrayEntry
*,ii
);
194 g_slist_free(entry
->list
);
195 if(entry
->tree_store
)
196 g_object_unref(G_OBJECT(entry
->tree_store
));
200 if(specific_folder_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
);
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)
236 /* Do nothing if foldercheck is not in use */
237 if(specific_folder_array_size
== 0)
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");
247 /* XML declarations */
248 xml_file_put_xml_decl(pfile
->fp
);
250 /* Build up XML tree */
253 tag
= xml_tag_new("foldercheckarray");
254 xmlnode
= xml_node_new(tag
, NULL
);
255 rootnode
= g_node_new(xmlnode
);
258 for(ii
= 0; ii
< specific_folder_array_size
; ii
++) {
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
)) {
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
));
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");
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)
308 GNode
*rootnode
, *node
, *branchnode
;
310 gboolean success
= FALSE
;
312 path
= foldercheck_get_array_path();
313 if(!is_file_exist(path
)) {
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
);
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
);
337 /* Process branch entries */
338 for(branchnode
= rootnode
->children
; branchnode
!= NULL
;
339 branchnode
= branchnode
->next
) {
342 SpecificFolderArrayEntry
*entry
= NULL
;
344 xmlnode
= branchnode
->data
;
345 if(g_strcmp0(xmlnode
->tag
->tag
, "branch") != 0) {
346 g_warning("tag name != \"branch\"");
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 */
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
);
395 if((list
== NULL
) || (item
== NULL
)) {
396 g_warning("did not find attribute \"identifier\" in tag "
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 */
412 /* Stolen from folder.c. Return value should NOT be freed. */
413 static gchar
*foldercheck_get_array_path(void)
415 static gchar
*filename
= NULL
;
418 filename
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
419 FOLDERCHECK_ARRAY
, NULL
);
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
);
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
);
450 g_slist_free(entry
->list
);
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
)
467 GtkWidget
*scrolledwin
;
468 GtkWidget
*confirm_area
;
470 GtkWidget
*cancel_button
;
471 GtkWidget
*ok_button
;
472 GtkTreeSelection
*selection
;
473 GtkTreeViewColumn
*column
;
474 GtkCellRenderer
*renderer
;
475 static GdkGeometry geometry
;
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
);
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
),
500 gtk_box_pack_start(GTK_BOX(vbox
), scrolledwin
, TRUE
, TRUE
, 0);
504 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_CLOSE
,
506 if(!folderopen_pixbuf
)
507 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_OPEN
,
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
);
517 foldercheck_set_tree(entry
);
518 gtk_tree_model_foreach(GTK_TREE_MODEL(entry
->tree_store
),
519 foldercheck_foreach_update_to_list
, entry
);
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
,
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);
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);
561 renderer
= gtk_cell_renderer_pixbuf_new();
562 gtk_tree_view_column_pack_start(column
, renderer
, FALSE
);
563 gtk_tree_view_column_set_attributes
565 "pixbuf", FOLDERCHECK_PIXBUF
,
566 "pixbuf-expander-open", FOLDERCHECK_PIXBUF_OPEN
,
567 "pixbuf-expander-closed", FOLDERCHECK_PIXBUF
,
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
,
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
);
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"),
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
,
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
);
627 /* sortable_set_sort_func */
628 static gint
foldercheck_folder_name_compare(GtkTreeModel
*model
,
629 GtkTreeIter
*a
, GtkTreeIter
*b
,
632 gchar
*str_a
= NULL
, *str_b
= NULL
;
634 FolderItem
*item_a
= NULL
, *item_b
= NULL
;
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
))
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
);
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
)
680 FolderItem
*item
= NULL
;
682 if (currently_selected
)
685 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
688 gtk_tree_model_get(model
, &iter
, FOLDERCHECK_FOLDERITEM
, &item
, -1);
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
713 static void foldercheck_set_tree(SpecificFolderArrayEntry
*entry
)
718 for(list
= folder_get_list(); list
!= NULL
; list
= list
->next
) {
719 folder
= FOLDER(list
->data
);
722 debug_print("Notification plugin::foldercheck_set_tree(): Found a NULL folder.\n");
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
) {
733 foldercheck_insert_gnode_in_store(entry
->tree_store
, folder
->node
, NULL
);
740 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry
->tree_store
),
741 FOLDERCHECK_FOLDERNAME
,
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
,
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
) {
780 if (!g_strcmp0(item
->name
, INBOX_DIR
))
784 if (!g_strcmp0(item
->name
, OUTBOX_DIR
))
788 if (!g_strcmp0(item
->name
, QUEUE_DIR
))
792 if (!g_strcmp0(item
->name
, TRASH_DIR
))
796 if (!g_strcmp0(item
->name
, DRAFT_DIR
))
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
);
809 name
= g_strdup(name
);
811 pixbuf
= item
->no_select
? foldernoselect_pixbuf
: folder_pixbuf
;
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
,
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
;
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);
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
),
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
;
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
,
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);
905 gtk_tree_model_get(model
, iter
, FOLDERCHECK_FOLDERITEM
, &item
, -1);
906 *list
= g_slist_prepend(*list
, item
);
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
,
920 gchar
*ident_tree
, *ident_list
;
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
);
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
)) {
945 gtk_tree_store_set(entry
->tree_store
, iter
, FOLDERCHECK_CHECK
,
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
)
957 gchar
*name
= (gchar
*) data
;
959 id
= notification_register_folder_specific_list(name
);
964 static gboolean
my_folder_update_hook(gpointer source
, gpointer data
)
966 FolderUpdateData
*hookdata
= (FolderUpdateData
*) source
;
968 if(hookdata
->update_flags
& FOLDER_REMOVE_FOLDERITEM
) {
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 */
983 static gboolean
key_pressed(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
985 if(event
&& (event
->keyval
== GDK_KEY_Escape
)) {
986 foldercheck_cancel(NULL
, data
);