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. */
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"
49 #include "notification_foldercheck.h"
51 /* enums and structures */
53 FOLDERCHECK_FOLDERNAME
,
54 FOLDERCHECK_FOLDERITEM
,
56 FOLDERCHECK_PIXBUF_OPEN
,
65 GtkTreeStore
*tree_store
;
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
;
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
*,
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
*,
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
,
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
;
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 "
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
);
152 if(!g_strcmp0(entry
->name
,node_name
))
158 /* Create an entry with the corresponding node name. */
159 entry
= g_new(SpecificFolderArrayEntry
, 1);
160 entry
->name
= g_strdup(node_name
);
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
,
173 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(entry
->tree_store
),
174 FOLDERCHECK_FOLDERNAME
,
175 foldercheck_folder_name_compare
,
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)
186 SpecificFolderArrayEntry
*entry
;
188 for(ii
= 0; ii
< specific_folder_array_size
; ii
++) {
189 entry
= g_array_index(specific_folder_array
,SpecificFolderArrayEntry
*,ii
);
193 g_slist_free(entry
->list
);
194 if(entry
->tree_store
)
195 g_object_unref(G_OBJECT(entry
->tree_store
));
199 if(specific_folder_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
);
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)
235 /* Do nothing if foldercheck is not in use */
236 if(specific_folder_array_size
== 0)
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");
246 /* XML declarations */
247 xml_file_put_xml_decl(pfile
->fp
);
249 /* Build up XML tree */
252 tag
= xml_tag_new("foldercheckarray");
253 xmlnode
= xml_node_new(tag
, NULL
);
254 rootnode
= g_node_new(xmlnode
);
257 for(ii
= 0; ii
< specific_folder_array_size
; ii
++) {
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
)) {
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
));
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");
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)
307 GNode
*rootnode
, *node
, *branchnode
;
309 gboolean success
= FALSE
;
311 path
= foldercheck_get_array_path();
312 if(!is_file_exist(path
)) {
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
);
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
);
336 /* Process branch entries */
337 for(branchnode
= rootnode
->children
; branchnode
!= NULL
;
338 branchnode
= branchnode
->next
) {
341 SpecificFolderArrayEntry
*entry
= NULL
;
343 xmlnode
= branchnode
->data
;
344 if(g_strcmp0(xmlnode
->tag
->tag
, "branch") != 0) {
345 g_warning("tag name != \"branch\"");
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 */
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
);
394 if((list
== NULL
) || (item
== NULL
)) {
395 g_warning("did not find attribute \"identifier\" in tag "
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 */
411 /* Stolen from folder.c. Return value should NOT be freed. */
412 static gchar
*foldercheck_get_array_path(void)
414 static gchar
*filename
= NULL
;
417 filename
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
418 FOLDERCHECK_ARRAY
, NULL
);
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
);
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
);
449 g_slist_free(entry
->list
);
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
)
466 GtkWidget
*scrolledwin
;
467 GtkWidget
*confirm_area
;
469 GtkWidget
*cancel_button
;
470 GtkWidget
*ok_button
;
471 GtkTreeSelection
*selection
;
472 GtkTreeViewColumn
*column
;
473 GtkCellRenderer
*renderer
;
474 static GdkGeometry geometry
;
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
);
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
),
499 gtk_box_pack_start(GTK_BOX(vbox
), scrolledwin
, TRUE
, TRUE
, 0);
503 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_CLOSE
,
505 if(!folderopen_pixbuf
)
506 stock_pixbuf_gdk(STOCK_PIXMAP_DIR_OPEN
,
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
);
516 foldercheck_set_tree(entry
);
517 gtk_tree_model_foreach(GTK_TREE_MODEL(entry
->tree_store
),
518 foldercheck_foreach_update_to_list
, entry
);
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
,
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);
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);
558 renderer
= gtk_cell_renderer_pixbuf_new();
559 gtk_tree_view_column_pack_start(column
, renderer
, FALSE
);
560 gtk_tree_view_column_set_attributes
562 "pixbuf", FOLDERCHECK_PIXBUF
,
563 "pixbuf-expander-open", FOLDERCHECK_PIXBUF_OPEN
,
564 "pixbuf-expander-closed", FOLDERCHECK_PIXBUF
,
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
,
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
);
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"),
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
,
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
);
624 /* sortable_set_sort_func */
625 static gint
foldercheck_folder_name_compare(GtkTreeModel
*model
,
626 GtkTreeIter
*a
, GtkTreeIter
*b
,
629 gchar
*str_a
= NULL
, *str_b
= NULL
;
631 FolderItem
*item_a
= NULL
, *item_b
= NULL
;
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
))
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
);
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
)
677 FolderItem
*item
= NULL
;
679 if (currently_selected
)
682 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
685 gtk_tree_model_get(model
, &iter
, FOLDERCHECK_FOLDERITEM
, &item
, -1);
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
710 static void foldercheck_set_tree(SpecificFolderArrayEntry
*entry
)
715 for(list
= folder_get_list(); list
!= NULL
; list
= list
->next
) {
716 folder
= FOLDER(list
->data
);
719 debug_print("Notification plugin::foldercheck_set_tree(): Found a NULL folder.\n");
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
) {
730 foldercheck_insert_gnode_in_store(entry
->tree_store
, folder
->node
, NULL
);
737 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry
->tree_store
),
738 FOLDERCHECK_FOLDERNAME
,
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
,
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
) {
777 if (!g_strcmp0(item
->name
, INBOX_DIR
))
781 if (!g_strcmp0(item
->name
, OUTBOX_DIR
))
785 if (!g_strcmp0(item
->name
, QUEUE_DIR
))
789 if (!g_strcmp0(item
->name
, TRASH_DIR
))
793 if (!g_strcmp0(item
->name
, DRAFT_DIR
))
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
);
806 name
= g_strdup(name
);
808 pixbuf
= item
->no_select
? foldernoselect_pixbuf
: folder_pixbuf
;
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
,
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
;
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);
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
),
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
;
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
,
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);
902 gtk_tree_model_get(model
, iter
, FOLDERCHECK_FOLDERITEM
, &item
, -1);
903 *list
= g_slist_prepend(*list
, item
);
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
,
917 gchar
*ident_tree
, *ident_list
;
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
);
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
)) {
942 gtk_tree_store_set(entry
->tree_store
, iter
, FOLDERCHECK_CHECK
,
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
)
954 gchar
*name
= (gchar
*) data
;
956 id
= notification_register_folder_specific_list(name
);
961 static gboolean
my_folder_update_hook(gpointer source
, gpointer data
)
963 FolderUpdateData
*hookdata
= (FolderUpdateData
*) source
;
965 if(hookdata
->update_flags
& FOLDER_REMOVE_FOLDERITEM
) {
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 */
980 static gboolean
key_pressed(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
982 if(event
&& (event
->keyval
== GDK_KEY_Escape
)) {
983 foldercheck_cancel(NULL
, data
);