2006-09-03 [colin] 2.4.0cvs137
[claws.git] / src / prefs_filtering.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
4  *
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 2 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29 #include <gtk/gtkoptionmenu.h>
30 #include <gdk/gdkkeysyms.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <errno.h>
35
36 #include "main.h"
37 #include "prefs_gtk.h"
38 #include "prefs_matcher.h"
39 #include "prefs_filtering.h"
40 #include "prefs_common.h"
41 #include "mainwindow.h"
42 #include "foldersel.h"
43 #include "manage_window.h"
44 #include "inc.h"
45 #include "utils.h"
46 #include "gtkutils.h"
47 #include "alertpanel.h"
48 #include "folder.h"
49 #include "filtering.h"
50 #include "addr_compl.h"
51 #include "colorlabel.h"
52 #include "manual.h"
53 #include "combobox.h"
54
55 #include "matcher_parser.h"
56 #include "matcher.h"
57 #include "prefs_filtering_action.h"
58
59 enum {
60         PREFS_FILTERING_ENABLED,
61         PREFS_FILTERING_NAME,
62         PREFS_FILTERING_ACCOUNT_ID,
63         PREFS_FILTERING_ACCOUNT_NAME,
64         PREFS_FILTERING_RULE,
65         PREFS_FILTERING_PROP,
66         N_PREFS_FILTERING_COLUMNS
67 };
68
69 struct _Filtering {
70         GtkWidget *window;
71
72         GtkWidget *help_btn;
73         GtkWidget *ok_btn;
74         GtkWidget *name_entry;
75         GtkWidget *account_label;
76         GtkWidget *account_combobox;
77         GtkListStore *account_combobox_list;
78         GtkWidget *cond_entry;
79         GtkWidget *action_entry;
80
81         GtkWidget *cond_list_view;
82
83         GtkTreeViewColumn *account_name_column;
84 };
85
86 typedef struct _Filtering Filtering;
87
88 static Filtering  filtering;
89
90 static GSList ** p_processing_list = NULL;
91
92 /* widget creating functions */
93 static void prefs_filtering_create              (void);
94
95 static void prefs_filtering_set_dialog  (const gchar *header,
96                                          const gchar *key);
97 static void prefs_filtering_set_list    (void);
98
99 /* callback functions */
100 static void prefs_filtering_register_cb (void);
101 static void prefs_filtering_substitute_cb       (void);
102 static void prefs_filtering_delete_cb   (void);
103 static void prefs_filtering_top         (void);
104 static void prefs_filtering_up          (void);
105 static void prefs_filtering_down        (void);
106 static void prefs_filtering_bottom      (void);
107 static gint prefs_filtering_deleted     (GtkWidget      *widget,
108                                          GdkEventAny    *event,
109                                          gpointer        data);
110 static gboolean prefs_filtering_key_pressed(GtkWidget   *widget,
111                                          GdkEventKey    *event,
112                                          gpointer        data);
113 static void prefs_filtering_cancel      (void);
114 static void prefs_filtering_ok          (void);
115
116 static void prefs_filtering_condition_define    (void);
117 static void prefs_filtering_action_define(void);
118 static gint prefs_filtering_list_view_set_row   (gint row, FilteringProp * prop);
119                                           
120 static void prefs_filtering_reset_dialog        (void);
121 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data);
122 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data);
123
124 static void delete_path(GSList ** p_filters, const gchar * path);
125
126
127 static GtkListStore* prefs_filtering_create_data_store  (void);
128 static gint prefs_filtering_list_view_insert_rule       (GtkListStore *list_store,
129                                                          gint row,
130                                                          gboolean enabled,
131                                                          const gchar *name, 
132                                                          gint account_id,
133                                                          const gchar *rule, 
134                                                          gboolean prop);
135 static gchar *prefs_filtering_list_view_get_rule        (GtkWidget *list, 
136                                                          gint row);
137 static void prefs_filtering_list_view_get_rule_name     (GtkWidget *list, 
138                                                          gint row,
139                                                          gboolean *enabled,
140                                                          gchar **name,
141                                                          gint *account_id);
142
143 static GtkWidget *prefs_filtering_list_view_create      (void);
144 static void prefs_filtering_create_list_view_columns    (GtkWidget *list_view);
145 static gint prefs_filtering_get_selected_row            (GtkWidget *list_view);
146 static gboolean prefs_filtering_list_view_select_row    (GtkWidget *list, gint row);
147
148 static gboolean prefs_filtering_selected                (GtkTreeSelection *selector,
149                                                          GtkTreeModel *model, 
150                                                          GtkTreePath *path,
151                                                          gboolean currently_selected,
152                                                          gpointer data);
153
154 static void prefs_filtering_account_option_menu_populate(void);
155
156 static gulong signal_id = 0; /* filtering.help_btn clicked signal */
157
158 void prefs_filtering_open(GSList ** p_processing,
159                           const gchar *title,
160                           const gchar *help_url_anchor,
161                           const gchar *header,
162                           const gchar *key,
163                           gboolean per_account_filtering)
164 {
165         if (prefs_rc_is_readonly(FILTERING_RC))
166                 return;
167
168         inc_lock();
169
170         if (!filtering.window) {
171                 prefs_filtering_create();
172         } else {
173                 gtk_list_store_clear(filtering.account_combobox_list);
174                 prefs_filtering_account_option_menu_populate();
175         }
176
177         gtk_tree_view_column_set_visible(filtering.account_name_column,
178                                                                          per_account_filtering);
179
180         manage_window_set_transient(GTK_WINDOW(filtering.window));
181         gtk_widget_grab_focus(filtering.ok_btn);
182         
183         if (title != NULL)
184                 gtk_window_set_title(GTK_WINDOW(filtering.window), title);
185         else
186                 gtk_window_set_title (GTK_WINDOW(filtering.window),
187                                       _("Filtering/Processing configuration"));
188
189         if (help_url_anchor != NULL) {
190                 if (signal_id != 0) {
191                         g_signal_handler_disconnect(
192                                         G_OBJECT(filtering.help_btn),
193                                         signal_id);
194                 }
195
196                 signal_id = g_signal_connect(G_OBJECT(filtering.help_btn),
197                                 "clicked",
198                                 G_CALLBACK(manual_open_with_anchor_cb),
199                                 (gchar*)help_url_anchor);
200         }
201         else {
202                 gtk_widget_set_sensitive(filtering.help_btn, FALSE);
203         }
204
205         p_processing_list = p_processing;
206         
207         prefs_filtering_set_dialog(header, key);
208         if (per_account_filtering) {
209                 gtk_widget_show(filtering.account_label);
210                 gtk_widget_show(filtering.account_combobox);
211         } else {
212                 gtk_widget_hide(filtering.account_label);
213                 gtk_widget_hide(filtering.account_combobox);
214                 combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
215         }
216
217         gtk_widget_show(filtering.window);
218
219         start_address_completion(NULL);
220 }
221
222 static void prefs_filtering_size_allocate_cb(GtkWidget *widget,
223                                          GtkAllocation *allocation)
224 {
225         g_return_if_fail(allocation != NULL);
226
227         prefs_common.filteringwin_width = allocation->width;
228         prefs_common.filteringwin_height = allocation->height;
229 }
230
231 /* prefs_filtering_close() - just to have one common exit point */
232 static void prefs_filtering_close(void)
233 {
234         end_address_completion();
235         
236         gtk_widget_hide(filtering.window);
237         inc_unlock();
238 }
239
240 static void prefs_filtering_account_option_menu_populate(void)
241 {
242         GList *accounts = NULL;
243         GtkTreeIter iter;
244
245         accounts = account_get_list();
246
247         g_return_if_fail(accounts != NULL);
248
249         COMBOBOX_ADD(filtering.account_combobox_list, Q_("Filtering Account Menu|All"), 0);
250         for (; accounts != NULL; accounts = accounts->next) {
251                 PrefsAccount *ac = (PrefsAccount *)accounts->data;
252
253                 COMBOBOX_ADD(filtering.account_combobox_list, ac->account_name, ac->account_id);
254         }
255 }
256
257 static GtkWidget *prefs_filtering_account_option_menu(Filtering *filtering)
258 {
259         GtkWidget *optmenu = NULL;
260         GtkWidget *optmenubox = NULL;
261         GtkListStore *menu = NULL;
262         
263         optmenubox = gtk_event_box_new();
264         optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
265         menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
266
267         filtering->account_combobox = optmenu;
268         filtering->account_combobox_list = menu;
269
270         prefs_filtering_account_option_menu_populate();
271
272         return optmenubox;
273 }
274
275 static void prefs_filtering_create(void)
276 {
277         GtkWidget *window;
278         GtkWidget *vbox;
279         GtkWidget *help_btn;
280         GtkWidget *cancel_btn;
281         GtkWidget *ok_btn;
282         GtkWidget *confirm_area;
283
284         GtkWidget *vbox1;
285         GtkWidget *reg_hbox;
286         GtkWidget *arrow;
287         GtkWidget *btn_hbox;
288
289         GtkWidget *name_label;
290         GtkWidget *name_entry;
291         GtkWidget *account_label;
292         GtkWidget *account_opt_menu;
293         GtkWidget *cond_label;
294         GtkWidget *cond_entry;
295         GtkWidget *cond_btn;
296         GtkWidget *action_label;
297         GtkWidget *action_entry;
298         GtkWidget *action_btn;
299
300         GtkWidget *reg_btn;
301         GtkWidget *subst_btn;
302         GtkWidget *del_btn;
303
304         GtkWidget *cond_hbox;
305         GtkWidget *cond_scrolledwin;
306         GtkWidget *cond_list_view;
307
308         GtkWidget *btn_vbox;
309         GtkWidget *spc_vbox;
310         GtkWidget *top_btn;
311         GtkWidget *up_btn;
312         GtkWidget *down_btn;
313         GtkWidget *bottom_btn;
314         GtkWidget *table;
315         static GdkGeometry geometry;
316
317         debug_print("Creating filtering configuration window...\n");
318
319         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
320         gtk_container_set_border_width (GTK_CONTAINER (window), 8);
321         gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
322         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
323         gtk_window_set_resizable(GTK_WINDOW (window), TRUE);
324
325         vbox = gtk_vbox_new (FALSE, 6);
326         gtk_widget_show (vbox);
327         gtk_container_add (GTK_CONTAINER (window), vbox);
328
329         gtkut_stock_button_set_create_with_help(&confirm_area, &help_btn,
330                         &cancel_btn, GTK_STOCK_CANCEL,
331                         &ok_btn, GTK_STOCK_OK,
332                         NULL, NULL);
333         gtk_widget_show (confirm_area);
334         gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
335         gtk_widget_grab_default (ok_btn);
336
337         gtk_window_set_title (GTK_WINDOW(window),
338                                     _("Filtering/Processing configuration"));
339
340         g_signal_connect(G_OBJECT(window), "delete_event",
341                          G_CALLBACK(prefs_filtering_deleted), NULL);
342         g_signal_connect(G_OBJECT(window), "size_allocate",
343                          G_CALLBACK(prefs_filtering_size_allocate_cb), NULL);
344         g_signal_connect(G_OBJECT(window), "key_press_event",
345                          G_CALLBACK(prefs_filtering_key_pressed), NULL);
346         MANAGE_WINDOW_SIGNALS_CONNECT (window);
347         g_signal_connect(G_OBJECT(ok_btn), "clicked",
348                          G_CALLBACK(prefs_filtering_ok), NULL);
349         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
350                          G_CALLBACK(prefs_filtering_cancel), NULL);
351
352         vbox1 = gtk_vbox_new (FALSE, VSPACING);
353         gtk_widget_show (vbox1);
354         gtk_box_pack_start (GTK_BOX (vbox), vbox1, FALSE, TRUE, 0);
355         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
356
357         table = gtk_table_new(3, 4, FALSE);
358         gtk_widget_show(table);
359         gtk_box_pack_start (GTK_BOX (vbox1), table, TRUE, TRUE, 0);
360         
361
362         name_label = gtk_label_new (_("Name: "));
363         gtk_widget_show (name_label);
364         gtk_misc_set_alignment (GTK_MISC (name_label), 1, 0.5);
365         gtk_table_attach (GTK_TABLE (table), name_label, 0, 1, 0, 1,
366                           (GtkAttachOptions) (GTK_FILL),
367                           (GtkAttachOptions) (0), 0, 0);
368
369         name_entry = gtk_entry_new ();
370         gtk_widget_show (name_entry);
371         gtk_table_attach (GTK_TABLE (table), name_entry, 1, 2, 0, 1,
372                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
373                           (GtkAttachOptions) (0), 0, 0);
374
375         account_label = gtk_label_new (_("Account:"));
376         gtk_widget_show (account_label);
377         gtk_misc_set_alignment (GTK_MISC (account_label), 1, 0.5);
378         gtk_table_attach (GTK_TABLE (table), account_label, 0, 1, 1, 2,
379                           (GtkAttachOptions) (GTK_FILL),
380                           (GtkAttachOptions) (0), 0, 0);
381
382         account_opt_menu = prefs_filtering_account_option_menu(&filtering);
383         gtk_widget_show (account_opt_menu);
384         gtk_table_attach (GTK_TABLE (table), account_opt_menu, 1, 2, 1, 2,
385                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
386                           (GtkAttachOptions) (0), 0, 0);
387         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
388
389         cond_label = gtk_label_new (_("Condition: "));
390         gtk_widget_show (cond_label);
391         gtk_misc_set_alignment (GTK_MISC (cond_label), 1, 0.5);
392         gtk_table_attach (GTK_TABLE (table), cond_label, 0, 1, 2, 3,
393                           (GtkAttachOptions) (GTK_FILL),
394                           (GtkAttachOptions) (0), 0, 0);
395
396         cond_entry = gtk_entry_new ();
397         gtk_widget_show (cond_entry);
398         gtk_table_attach (GTK_TABLE (table), cond_entry, 1, 2, 2, 3,
399                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
400                           (GtkAttachOptions) (0), 0, 0);
401
402         cond_btn = gtk_button_new_with_label (_(" Define... "));
403         gtk_widget_show (cond_btn);
404         gtk_table_attach (GTK_TABLE (table), cond_btn, 2, 3, 2, 3,
405                           (GtkAttachOptions) (GTK_FILL),
406                           (GtkAttachOptions) (0), 2, 2);
407         g_signal_connect(G_OBJECT (cond_btn), "clicked",
408                          G_CALLBACK(prefs_filtering_condition_define),
409                          NULL);
410
411         action_label = gtk_label_new (_("Action: "));
412         gtk_widget_show (action_label);
413         gtk_misc_set_alignment (GTK_MISC (action_label), 1, 0.5);
414         gtk_table_attach (GTK_TABLE (table), action_label, 0, 1, 3, 4,
415                           (GtkAttachOptions) (GTK_FILL),
416                           (GtkAttachOptions) (0), 0, 0);
417
418         action_entry = gtk_entry_new ();
419         gtk_widget_show (action_entry);
420         gtk_table_attach (GTK_TABLE (table), action_entry, 1, 2, 3, 4,
421                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
422                           (GtkAttachOptions) (0), 0, 0);
423
424         action_btn = gtk_button_new_with_label (_(" Define... "));
425         gtk_widget_show (action_btn);
426         gtk_table_attach (GTK_TABLE (table), action_btn, 2, 3, 3, 4,
427                           (GtkAttachOptions) (GTK_FILL),
428                           (GtkAttachOptions) (0), 2, 2);
429         g_signal_connect(G_OBJECT (action_btn), "clicked",
430                          G_CALLBACK(prefs_filtering_action_define),
431                          NULL);
432                          
433         /* register / substitute / delete */
434         reg_hbox = gtk_hbox_new (FALSE, 4);
435         gtk_widget_show (reg_hbox);
436         gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox, FALSE, FALSE, 0);
437
438         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
439         gtk_widget_show (arrow);
440         gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0);
441         gtk_widget_set_size_request (arrow, -1, 16);
442
443         btn_hbox = gtk_hbox_new (TRUE, 4);
444         gtk_widget_show (btn_hbox);
445         gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0);
446
447         reg_btn = gtk_button_new_from_stock (GTK_STOCK_ADD);
448         gtk_widget_show (reg_btn);
449         gtk_box_pack_start (GTK_BOX (btn_hbox), reg_btn, FALSE, TRUE, 0);
450         g_signal_connect(G_OBJECT (reg_btn), "clicked",
451                          G_CALLBACK(prefs_filtering_register_cb), NULL);
452
453         subst_btn = gtkut_get_replace_btn (_("Replace"));
454         gtk_widget_show (subst_btn);
455         gtk_box_pack_start (GTK_BOX (btn_hbox), subst_btn, FALSE, TRUE, 0);
456         g_signal_connect(G_OBJECT (subst_btn), "clicked",
457                          G_CALLBACK(prefs_filtering_substitute_cb),
458                          NULL);
459
460         del_btn = gtk_button_new_from_stock (GTK_STOCK_DELETE);
461         gtk_widget_show (del_btn);
462         gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0);
463         g_signal_connect(G_OBJECT (del_btn), "clicked",
464                         G_CALLBACK(prefs_filtering_delete_cb), NULL);
465
466         cond_hbox = gtk_hbox_new (FALSE, 8);
467         gtk_widget_show (cond_hbox);
468         gtk_box_pack_start (GTK_BOX (vbox), cond_hbox, TRUE, TRUE, 0);
469
470         cond_scrolledwin = gtk_scrolled_window_new (NULL, NULL);
471         gtk_widget_show (cond_scrolledwin);
472         gtk_widget_set_size_request (cond_scrolledwin, -1, 150);
473         gtk_box_pack_start (GTK_BOX (cond_hbox), cond_scrolledwin,
474                             TRUE, TRUE, 0);
475         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cond_scrolledwin),
476                                         GTK_POLICY_AUTOMATIC,
477                                         GTK_POLICY_AUTOMATIC);
478
479         cond_list_view = prefs_filtering_list_view_create();    
480         gtk_widget_show (cond_list_view);
481         gtk_container_add (GTK_CONTAINER (cond_scrolledwin), cond_list_view);
482
483         btn_vbox = gtk_vbox_new (FALSE, 8);
484         gtk_widget_show (btn_vbox);
485         gtk_box_pack_start (GTK_BOX (cond_hbox), btn_vbox, FALSE, FALSE, 0);
486
487         top_btn = gtk_button_new_from_stock (GTK_STOCK_GOTO_TOP);
488         gtk_widget_show (top_btn);
489         gtk_box_pack_start (GTK_BOX (btn_vbox), top_btn, FALSE, FALSE, 0);
490         g_signal_connect(G_OBJECT (top_btn), "clicked",
491                          G_CALLBACK(prefs_filtering_top), NULL);
492
493         PACK_VSPACER (btn_vbox, spc_vbox, VSPACING_NARROW_2);
494
495         up_btn = gtk_button_new_from_stock (GTK_STOCK_GO_UP);
496         gtk_widget_show (up_btn);
497         gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0);
498         g_signal_connect(G_OBJECT (up_btn), "clicked",
499                          G_CALLBACK(prefs_filtering_up), NULL);
500
501         down_btn = gtk_button_new_from_stock (GTK_STOCK_GO_DOWN);
502         gtk_widget_show (down_btn);
503         gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0);
504         g_signal_connect(G_OBJECT (down_btn), "clicked",
505                          G_CALLBACK(prefs_filtering_down), NULL);
506
507         PACK_VSPACER (btn_vbox, spc_vbox, VSPACING_NARROW_2);
508
509         bottom_btn = gtk_button_new_from_stock (GTK_STOCK_GOTO_BOTTOM);
510         gtk_widget_show (bottom_btn);
511         gtk_box_pack_start (GTK_BOX (btn_vbox), bottom_btn, FALSE, FALSE, 0);
512         g_signal_connect(G_OBJECT (bottom_btn), "clicked",
513                          G_CALLBACK(prefs_filtering_bottom), NULL);
514
515         if (!geometry.min_height) {
516                 geometry.min_width = 500;
517                 geometry.min_height = 400;
518         }
519
520         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
521                                       GDK_HINT_MIN_SIZE);
522         gtk_widget_set_size_request(window, prefs_common.filteringwin_width,
523                                     prefs_common.filteringwin_height);
524
525         gtk_widget_show_all(window);
526
527         filtering.window    = window;
528         filtering.help_btn = help_btn;
529         filtering.ok_btn = ok_btn;
530
531         filtering.name_entry     = name_entry;
532         filtering.cond_entry     = cond_entry;
533         filtering.action_entry   = action_entry;
534         filtering.cond_list_view = cond_list_view;
535         filtering.account_label  = account_label;
536 }
537
538 static void rename_path(GSList * filters,
539                         const gchar * old_path, const gchar * new_path);
540
541 void prefs_filtering_rename_path(const gchar *old_path, const gchar *new_path)
542 {
543         GList * cur;
544         const gchar *paths[2] = {NULL, NULL};
545         paths[0] = old_path;
546         paths[1] = new_path;
547         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
548                 Folder *folder;
549                 folder = (Folder *) cur->data;
550                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
551                                 prefs_filtering_rename_path_func, paths);
552         }
553         
554         rename_path(pre_global_processing, old_path, new_path);
555         rename_path(post_global_processing, old_path, new_path);
556         rename_path(filtering_rules, old_path, new_path);
557         
558         prefs_matcher_write_config();
559 }
560
561 static void rename_path(GSList * filters,
562                         const gchar * old_path, const gchar * new_path)
563 {
564         gchar *base;
565         gchar *prefix;
566         gchar *suffix;
567         gchar *dest_path;
568         gchar *old_path_with_sep;
569         gint destlen;
570         gint prefixlen;
571         gint oldpathlen;
572         GSList * action_cur;
573         GSList * cur;
574
575         oldpathlen = strlen(old_path);
576         old_path_with_sep = g_strconcat(old_path,G_DIR_SEPARATOR_S,NULL);
577
578         for (cur = filters; cur != NULL; cur = cur->next) {
579                 FilteringProp   *filtering = (FilteringProp *)cur->data;
580                 
581                 for(action_cur = filtering->action_list ; action_cur != NULL ;
582                     action_cur = action_cur->next) {
583
584                         FilteringAction *action = action_cur->data;
585                         
586                         if (!action->destination) continue;
587                         
588                         destlen = strlen(action->destination);
589                         
590                         if (destlen > oldpathlen) {
591                                 prefixlen = destlen - oldpathlen;
592                                 suffix = action->destination + prefixlen;
593                                 
594                                 if (!strncmp(old_path, suffix, oldpathlen)) {
595                                         prefix = g_malloc0(prefixlen + 1);
596                                         strncpy2(prefix, action->destination, prefixlen);
597                                         
598                                         base = suffix + oldpathlen;
599                                         while (*base == G_DIR_SEPARATOR) base++;
600                                         if (*base == '\0')
601                                                 dest_path = g_strconcat(prefix,
602                                                     G_DIR_SEPARATOR_S,
603                                                     new_path, NULL);
604                                         else
605                                                 dest_path = g_strconcat(prefix,
606                                                     G_DIR_SEPARATOR_S,
607                                                     new_path,
608                                                     G_DIR_SEPARATOR_S,
609                                                     base, NULL);
610                                         
611                                         g_free(prefix);
612                                         g_free(action->destination);
613                                         action->destination = dest_path;
614                                 } else { /* for non-leaf folders */
615                                         /* compare with trailing slash */
616                                         if (!strncmp(old_path_with_sep, action->destination, oldpathlen+1)) {
617                                                 
618                                                 suffix = action->destination + oldpathlen + 1;
619                                                 dest_path = g_strconcat(new_path,
620                                                     G_DIR_SEPARATOR_S,
621                                                     suffix, NULL);
622                                                 g_free(action->destination);
623                                                 action->destination = dest_path;
624                                         }
625                                 }
626                         } else {
627                                 /* folder-moving a leaf */
628                                 if (!strcmp(old_path, action->destination)) {
629                                         dest_path = g_strdup(new_path);
630                                         g_free(action->destination);
631                                         action->destination = dest_path;
632                                 }
633                         }
634                 }
635         }
636 }
637
638 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data)
639 {
640         GSList *filters;
641         const gchar * old_path;
642         const gchar * new_path;
643         const gchar ** paths;
644         FolderItem *item;
645         
646         paths = data;
647         old_path = paths[0];
648         new_path = paths[1];
649
650         g_return_val_if_fail(old_path != NULL, FALSE);
651         g_return_val_if_fail(new_path != NULL, FALSE);
652         g_return_val_if_fail(node != NULL, FALSE);
653
654         item = node->data;
655         if (!item || !item->prefs)
656                 return FALSE;
657         filters = item->prefs->processing;
658
659         rename_path(filters, old_path, new_path);
660
661         return FALSE;
662 }
663
664 void prefs_filtering_delete_path(const gchar *path)
665 {
666         GList * cur;
667         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
668                 Folder *folder;
669                 folder = (Folder *) cur->data;
670                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
671                                 prefs_filtering_delete_path_func, (gchar *)path);
672         }
673         delete_path(&pre_global_processing, path);
674         delete_path(&post_global_processing, path);
675         delete_path(&filtering_rules, path);
676         
677         prefs_matcher_write_config();
678 }
679
680 static void delete_path(GSList ** p_filters, const gchar * path)
681 {
682         GSList * filters;
683         GSList * duplist;
684         gchar *suffix;
685         gint destlen;
686         gint prefixlen;
687         gint pathlen;
688         GSList * action_cur;
689         GSList * cur;
690         
691         filters = *p_filters;
692         pathlen = strlen(path);
693         duplist = g_slist_copy(filters);
694         for (cur = duplist ; cur != NULL; cur = g_slist_next(cur)) {
695                 FilteringProp *filtering = (FilteringProp *) cur->data;
696                 
697                 for(action_cur = filtering->action_list ; action_cur != NULL ;
698                     action_cur = action_cur->next) {
699                 
700                         FilteringAction *action;
701                         
702                         action = action_cur->data;
703                         
704                         if (!action->destination) continue;
705                         
706                         destlen = strlen(action->destination);
707                         
708                         if (destlen > pathlen) {
709                                 prefixlen = destlen - pathlen;
710                                 suffix = action->destination + prefixlen;
711                                 
712                                 if (suffix && !strncmp(path, suffix, pathlen)) {
713                                         filteringprop_free(filtering);
714                                         filters = g_slist_remove(filters, filtering);
715                                 }
716                         } else if (strcmp(action->destination, path) == 0) {
717                                 filteringprop_free(filtering);
718                                 filters = g_slist_remove(filters, filtering);
719                         }
720                 }
721         }                
722         g_slist_free(duplist);
723         
724         * p_filters = filters;
725 }
726
727 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data)
728 {
729         const gchar *path = data;
730         FolderItem *item;
731         GSList ** p_filters;
732         
733         g_return_val_if_fail(path != NULL, FALSE);
734         g_return_val_if_fail(node != NULL, FALSE);
735
736         item = node->data;
737         if (!item || !item->prefs)
738                 return FALSE;
739         p_filters = &item->prefs->processing;
740         
741         delete_path(p_filters, path);
742
743         return FALSE;
744 }
745
746 static void prefs_filtering_set_dialog(const gchar *header, const gchar *key)
747 {
748         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
749         GSList *cur;
750         GSList * prefs_filtering;
751         gchar *cond_str;
752         GtkListStore *list_store;
753         
754         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(list_view));
755         gtk_list_store_clear(list_store);
756
757         /* add the place holder (New) at row 0 */
758         prefs_filtering_list_view_insert_rule(list_store, -1, 
759                                               FALSE,
760                                               _("(New)"),
761                                                   0,
762                                               _("(New)"),
763                                               FALSE);
764
765         prefs_filtering = *p_processing_list;
766
767         for(cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur)) {
768                 FilteringProp * prop = (FilteringProp *) cur->data;
769
770                 cond_str = filteringprop_to_string(prop);
771                 subst_char(cond_str, '\t', ':');
772
773                 prefs_filtering_list_view_insert_rule(list_store, -1, 
774                                                       prop->enabled,
775                                                       prop->name,
776                                                           prop->account_id,
777                                                       cond_str, TRUE);
778                 
779                 g_free(cond_str);
780         }
781
782         prefs_filtering_reset_dialog();
783
784         if (header && key) {
785                 gchar * quoted_key;
786                 gchar *match_str;
787
788                 quoted_key = matcher_quote_str(key);
789                 
790                 match_str = g_strconcat(header, " ", get_matchparser_tab_str(MATCHTYPE_MATCHCASE),
791                                         " \"", quoted_key, "\"", NULL);
792                 g_free(quoted_key);
793                 
794                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), match_str);
795                 g_free(match_str);
796         }
797 }
798
799 static void prefs_filtering_reset_dialog(void)
800 {
801         gtk_entry_set_text(GTK_ENTRY(filtering.name_entry), "");
802         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
803         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), "");
804         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), "");
805 }
806
807 static void prefs_filtering_set_list(void)
808 {
809         gint row = 1;
810         FilteringProp *prop;
811         GSList * cur;
812         gchar * filtering_str;
813         GSList * prefs_filtering;
814
815         prefs_filtering = *p_processing_list;
816         for (cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur))
817                 filteringprop_free((FilteringProp *) cur->data);
818         g_slist_free(prefs_filtering);
819         prefs_filtering = NULL;
820         
821
822         while (NULL != (filtering_str = prefs_filtering_list_view_get_rule
823                                                 (filtering.cond_list_view, row))) {
824                 /* FIXME: this strcmp() is bogus: "(New)" should never
825                  * be inserted in the storage */
826                 if (strcmp(filtering_str, _("(New)")) != 0) {
827                         gboolean enabled;
828                         gchar *name;
829                         gint account_id = 0;
830
831                         prefs_filtering_list_view_get_rule_name(
832                                         filtering.cond_list_view, row,
833                                         &enabled, &name, &account_id);
834                         prop = matcher_parser_get_filtering(filtering_str);
835                         g_free(filtering_str);
836                         if (prop) {
837                                 prop->enabled = enabled;
838                                 prop->name = name;
839                                 prop->account_id = account_id;
840                                 prefs_filtering = 
841                                         g_slist_append(prefs_filtering, prop);
842                         }
843                 }
844                 
845                 row++;
846         }                               
847         
848         *p_processing_list = prefs_filtering;
849 }
850
851 static gint prefs_filtering_list_view_set_row(gint row, FilteringProp * prop)
852 {
853         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
854         gchar *str;
855         GtkListStore *list_store;
856         gchar *name = NULL;
857         gint account_id = 0;
858         gboolean enabled = TRUE;
859
860         str = filteringprop_to_string(prop);
861         if (str == NULL)
862                 return -1;
863
864         if (prop) {
865                 if (prop->name)
866                         name = prop->name;
867                 account_id = prop->account_id;
868                 enabled = prop->enabled;
869         }
870
871         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(list_view));
872
873         row = prefs_filtering_list_view_insert_rule(list_store, row,
874                                                     enabled,
875                                                     name,
876                                                     account_id,
877                                                     str,
878                                                     prop != NULL);
879
880         g_free(str);
881
882         return row;
883 }
884
885 static void prefs_filtering_condition_define_done(MatcherList * matchers)
886 {
887         gchar * str;
888
889         if (matchers == NULL)
890                 return;
891
892         str = matcherlist_to_string(matchers);
893
894         if (str != NULL) {
895                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), str);
896                 g_free(str);
897         }
898 }
899
900 static void prefs_filtering_condition_define(void)
901 {
902         gchar * cond_str;
903         MatcherList * matchers = NULL;
904
905         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
906
907         if (*cond_str != '\0') {
908                 matchers = matcher_parser_get_cond(cond_str);
909                 if (matchers == NULL)
910                         alertpanel_error(_("Condition string is not valid."));
911         }
912         
913         g_free(cond_str);
914
915         prefs_matcher_open(matchers, prefs_filtering_condition_define_done);
916
917         if (matchers != NULL)
918                 matcherlist_free(matchers);
919 }
920
921 static void prefs_filtering_action_define_done(GSList * action_list)
922 {
923         gchar * str;
924
925         if (action_list == NULL)
926                 return;
927
928         str = filteringaction_list_to_string(action_list);
929
930         if (str != NULL) {
931                 gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), str);
932                 g_free(str);
933         }
934 }
935
936 static void prefs_filtering_action_define(void)
937 {
938         gchar * action_str;
939         GSList * action_list = NULL;
940
941         action_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
942
943         if (*action_str != '\0') {
944                 action_list = matcher_parser_get_action_list(action_str);
945                 if (action_list == NULL)
946                         alertpanel_error(_("Action string is not valid."));
947         }
948         
949         g_free(action_str);
950
951         prefs_filtering_action_open(action_list,
952             prefs_filtering_action_define_done);
953
954         if (action_list != NULL) {
955                 GSList * cur;
956                 for(cur = action_list ; cur != NULL ; cur = cur->next) {
957                         filteringaction_free(cur->data);
958                 }
959         }
960 }
961
962
963 /* register / substitute delete buttons */
964
965
966 static FilteringProp * prefs_filtering_dialog_to_filtering(gboolean alert)
967 {
968         MatcherList * cond;
969         gboolean enabled = TRUE;
970         gchar * name = NULL;
971         gint account_id = 0;
972         gchar * cond_str = NULL;
973         gchar * action_str = NULL;
974         FilteringProp * prop = NULL;
975         GSList * action_list;
976
977         name = gtk_editable_get_chars(GTK_EDITABLE(filtering.name_entry), 0, -1);
978         
979         account_id = combobox_get_active_data(GTK_COMBO_BOX(filtering.account_combobox));
980
981         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
982         if (*cond_str == '\0') {
983                 if(alert == TRUE) alertpanel_error(_("Condition string is empty."));
984                 goto fail;
985         }
986         
987         action_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
988         if (*action_str == '\0') {
989                 if(alert == TRUE) alertpanel_error(_("Action string is empty."));
990                 goto fail;
991         }
992
993         cond = matcher_parser_get_cond(cond_str);
994
995         if (cond == NULL) {
996                 if(alert == TRUE) alertpanel_error(_("Condition string is not valid."));
997                 goto fail;
998         }
999         
1000         action_list = matcher_parser_get_action_list(action_str);
1001         
1002
1003         if (action_list == NULL) {
1004                 if(alert == TRUE) alertpanel_error(_("Action string is not valid."));
1005                 goto fail;
1006         }
1007
1008         prop = filteringprop_new(enabled, name, account_id, cond, action_list);
1009
1010 fail:
1011         g_free(name);
1012         g_free(cond_str);
1013         g_free(action_str);
1014         return prop;
1015 }
1016
1017 static void prefs_filtering_register_cb(void)
1018 {
1019         FilteringProp * prop;
1020         
1021         prop = prefs_filtering_dialog_to_filtering(TRUE);
1022         if (prop == NULL)
1023                 return;
1024         prefs_filtering_list_view_set_row(-1, prop);
1025         
1026         filteringprop_free(prop);
1027
1028         prefs_filtering_reset_dialog();
1029 }
1030
1031 static void prefs_filtering_substitute_cb(void)
1032 {
1033         gint selected_row = prefs_filtering_get_selected_row
1034                 (filtering.cond_list_view);
1035         FilteringProp *prop;
1036         
1037         if (selected_row <= 0)
1038                 return;
1039
1040         prop = prefs_filtering_dialog_to_filtering(TRUE);
1041         if (prop == NULL) 
1042                 return;
1043         prefs_filtering_list_view_set_row(selected_row, prop);
1044
1045         filteringprop_free(prop);
1046
1047         prefs_filtering_reset_dialog();
1048 }
1049
1050 static void prefs_filtering_delete_cb(void)
1051 {
1052         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
1053         GtkTreeModel *model;
1054         GtkTreeIter iter;
1055         gint row;
1056         
1057         row = prefs_filtering_get_selected_row(filtering.cond_list_view);
1058         if (row <= 0) 
1059                 return; 
1060
1061         if (alertpanel(_("Delete rule"),
1062                        _("Do you really want to delete this rule?"),
1063                        GTK_STOCK_CANCEL, "+"GTK_STOCK_DELETE, NULL) == G_ALERTDEFAULT)
1064                 return;
1065
1066         model = gtk_tree_view_get_model(list_view);     
1067         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1068                 return;
1069
1070         gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1071
1072         prefs_filtering_reset_dialog();
1073 }
1074
1075 static void prefs_filtering_top(void)
1076 {
1077         gint row;
1078         GtkTreeIter top, sel;
1079         GtkTreeModel *model;
1080
1081         row = prefs_filtering_get_selected_row(filtering.cond_list_view);
1082         if (row <= 1) 
1083                 return;
1084
1085         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));               
1086         
1087         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, 0)
1088         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
1089                 return;
1090
1091         gtk_list_store_move_after(GTK_LIST_STORE(model), &sel, &top);
1092         prefs_filtering_list_view_select_row(filtering.cond_list_view, 1);
1093 }
1094
1095 static void prefs_filtering_up(void)
1096 {
1097         gint row;
1098         GtkTreeIter top, sel;
1099         GtkTreeModel *model;
1100
1101         row = prefs_filtering_get_selected_row(filtering.cond_list_view);
1102         if (row <= 1) 
1103                 return;
1104                 
1105         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1106
1107         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row - 1)
1108         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
1109                 return;
1110
1111         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
1112         prefs_filtering_list_view_select_row(filtering.cond_list_view, row - 1);
1113 }
1114
1115 static void prefs_filtering_down(void)
1116 {
1117         gint row, n_rows;
1118         GtkTreeIter top, sel;
1119         GtkTreeModel *model;
1120
1121         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1122         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1123         row = prefs_filtering_get_selected_row(filtering.cond_list_view);
1124         if (row < 1 || row >= n_rows - 1)
1125                 return;
1126
1127         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
1128         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row + 1))
1129                 return;
1130                         
1131         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
1132         prefs_filtering_list_view_select_row(filtering.cond_list_view, row + 1);
1133 }
1134
1135 static void prefs_filtering_bottom(void)
1136 {
1137         gint row, n_rows;
1138         GtkTreeIter top, sel;
1139         GtkTreeModel *model;
1140
1141         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1142         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1143         row = prefs_filtering_get_selected_row(filtering.cond_list_view);
1144         if (row < 1 || row >= n_rows - 1)
1145                 return;
1146
1147         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
1148         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, n_rows - 1))
1149                 return;
1150
1151         gtk_list_store_move_after(GTK_LIST_STORE(model), &top, &sel);           
1152         prefs_filtering_list_view_select_row(filtering.cond_list_view, n_rows - 1);
1153 }
1154
1155 static void prefs_filtering_select_set(FilteringProp *prop)
1156 {
1157         gchar *matcher_str;
1158         gchar *action_str;
1159
1160         prefs_filtering_reset_dialog();
1161
1162         matcher_str = matcherlist_to_string(prop->matchers);
1163         if (matcher_str == NULL) {
1164                 return;
1165         }
1166         
1167         if (prop->name != NULL)
1168                 gtk_entry_set_text(GTK_ENTRY(filtering.name_entry), prop->name);
1169
1170         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), prop->account_id);
1171                 
1172         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), matcher_str);
1173
1174         action_str = filteringaction_list_to_string(prop->action_list);
1175         if (matcher_str == NULL) {
1176                 return;
1177         }
1178         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), action_str);
1179
1180         g_free(action_str);
1181         g_free(matcher_str);
1182 }
1183
1184 static gint prefs_filtering_deleted(GtkWidget *widget, GdkEventAny *event,
1185                                  gpointer data)
1186 {
1187         prefs_filtering_cancel();
1188         return TRUE;
1189 }
1190
1191 static gboolean prefs_filtering_key_pressed(GtkWidget *widget, GdkEventKey *event,
1192                                      gpointer data)
1193 {
1194         if (event && event->keyval == GDK_Escape) {
1195                 prefs_filtering_cancel();
1196                 return TRUE;                    
1197         }
1198         return FALSE;
1199 }
1200
1201 static void prefs_filtering_ok(void)
1202 {
1203         FilteringProp * prop;
1204         gchar * str;
1205         gchar * filtering_str;
1206         gint row = 1;
1207         AlertValue val;
1208         
1209         prop = prefs_filtering_dialog_to_filtering(FALSE);
1210         if (prop != NULL) {
1211                 str = filteringprop_to_string(prop);
1212
1213                 while (NULL != (filtering_str = (prefs_filtering_list_view_get_rule
1214                                                         (filtering.cond_list_view,
1215                                                          row)))) {
1216                         if (strcmp(filtering_str, str) == 0)
1217                                 break;
1218                         row++;
1219                         g_free(filtering_str);
1220                 }       
1221
1222                 if (!filtering_str) {
1223                         val = alertpanel(_("Entry not saved"),
1224                                  _("The entry was not saved. Close anyway?"),
1225                                  GTK_STOCK_CLOSE, _("+_Continue editing"), NULL);
1226                         if (G_ALERTDEFAULT != val) {
1227                                 g_free(filtering_str);
1228                                 g_free(str); /* fixed two leaks: huzzah! */
1229                                 filteringprop_free(prop);
1230                                 return;
1231                         }
1232                 }               
1233
1234                 g_free(filtering_str);
1235                 g_free(str);
1236                 filteringprop_free(prop); /* fixed a leak: huzzah! */
1237         } else {
1238                 gchar *name, *condition, *action;
1239                 name = gtk_editable_get_chars(GTK_EDITABLE(filtering.name_entry), 0, -1);
1240                 condition = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
1241                 action = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
1242                 if (strlen(name) || 
1243                     strlen(condition) || 
1244                     strlen(action)) {
1245                         val = alertpanel(_("Entry not saved"),
1246                                  _("The entry was not saved. Close anyway?"),
1247                                  GTK_STOCK_CLOSE, _("+_Continue editing"), NULL);
1248                         if (G_ALERTDEFAULT != val) {
1249                                 g_free(name);
1250                                 g_free(condition);
1251                                 g_free(action);
1252                                 return;
1253                         }
1254                 }
1255                 g_free(name);
1256                 g_free(condition);
1257                 g_free(action);
1258         }
1259         prefs_filtering_set_list();
1260         prefs_matcher_write_config();
1261         prefs_filtering_close();
1262 }
1263
1264 static void prefs_filtering_cancel(void)
1265 {
1266         prefs_matcher_read_config();
1267         prefs_filtering_close();
1268 }
1269
1270 static GtkListStore* prefs_filtering_create_data_store(void)
1271 {
1272         return gtk_list_store_new(N_PREFS_FILTERING_COLUMNS,
1273                                   G_TYPE_BOOLEAN,
1274                                   G_TYPE_STRING,
1275                                   G_TYPE_INT,
1276                                   G_TYPE_STRING,
1277                                   G_TYPE_STRING,
1278                                   G_TYPE_BOOLEAN,
1279                                  -1);
1280 }
1281
1282 /*!
1283  *\brief        Insert filtering rule into store. Note that we access the
1284  *              tree view / store by index, which is a bit suboptimal, but
1285  *              at least it made GTK 2 porting easier.
1286  *
1287  *\param        list_store Store to operate on
1288  *\param        row -1 to add a new rule to store, else change an existing
1289  *              row
1290  *\param        enabled TRUE if rule is enabled
1291  *\param        name The Name of rule
1292  *\param        account_id The account ID
1293  *\param        rule String representation of rule
1294  *\param        prop TRUE if valid filtering rule; if FALSE it's the first
1295  *              entry in the store ("(New)").
1296  *
1297  *\return       int Row of inserted / changed rule.
1298  */
1299 static gint prefs_filtering_list_view_insert_rule(GtkListStore *list_store,
1300                                                   gint row,
1301                                                   gboolean enabled,
1302                                                   const gchar *name,
1303                                                   gint account_id,
1304                                                   const gchar *rule,
1305                                                   gboolean prop) 
1306 {
1307         GtkTreeIter iter;
1308         gchar *account_name;
1309
1310         /* check if valid row at all */
1311         if (row >= 0) {
1312                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
1313                                                    &iter, NULL, row))
1314                         row = -1;                                                  
1315         }
1316
1317         if (account_id > 0) {
1318                 account_name = account_find_from_id(account_id)->account_name;
1319         } else {
1320                 account_name = (gchar *)Q_("Filtering Account Menu|All");
1321         }
1322
1323         if (row < 0) {
1324                 /* append new */
1325                 gtk_list_store_append(list_store, &iter);
1326                 gtk_list_store_set(list_store, &iter, 
1327                                    PREFS_FILTERING_ENABLED, enabled,
1328                                    PREFS_FILTERING_NAME, name,
1329                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1330                                    PREFS_FILTERING_ACCOUNT_NAME, account_name,
1331                                    PREFS_FILTERING_RULE, rule,
1332                                    PREFS_FILTERING_PROP, prop,
1333                                    -1);
1334                 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store),
1335                                                       NULL) - 1;
1336         } else {
1337                 /* change existing */
1338                 gtk_list_store_set(list_store, &iter, 
1339                                    PREFS_FILTERING_ENABLED, enabled,
1340                                    PREFS_FILTERING_NAME, name,
1341                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1342                                    PREFS_FILTERING_ACCOUNT_NAME, account_name,
1343                                    PREFS_FILTERING_RULE, rule,
1344                                    -1);
1345                 return row;                                
1346         }
1347 }
1348
1349 /*!
1350  *\return       gchar * Rule at specified row - should be freed.
1351  */
1352 static gchar *prefs_filtering_list_view_get_rule(GtkWidget *list, gint row)
1353 {       
1354         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1355         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1356         GtkTreeIter iter;
1357         gchar *result;
1358
1359         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1360                 return NULL;
1361         
1362         gtk_tree_model_get(model, &iter, 
1363                            PREFS_FILTERING_RULE, &result,
1364                            -1);
1365         
1366         return result;
1367 }
1368
1369 static void prefs_filtering_list_view_get_rule_name(GtkWidget *list, gint row,
1370                                 gboolean *enabled, gchar **name, gint *account_id)
1371 {       
1372         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1373         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1374         GtkTreeIter iter;
1375
1376         *enabled = TRUE;
1377         *name = NULL;
1378
1379         if (gtk_tree_model_iter_nth_child(model, &iter, NULL, row)) {
1380                 gtk_tree_model_get(model, &iter, 
1381                                    PREFS_FILTERING_ENABLED, enabled,
1382                                    PREFS_FILTERING_NAME, name,
1383                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1384                                    -1);
1385         }
1386 }
1387
1388 /*!
1389  *\brief        Create list view for filtering
1390  */
1391 static GtkWidget *prefs_filtering_list_view_create(void)
1392 {
1393         GtkTreeView *list_view;
1394         GtkTreeSelection *selector;
1395
1396         list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL
1397                 (prefs_filtering_create_data_store())));
1398         
1399         gtk_tree_view_set_rules_hint(list_view, prefs_common.enable_rules_hint);
1400         gtk_tree_view_set_reorderable(list_view, TRUE);
1401         
1402         selector = gtk_tree_view_get_selection(list_view);
1403         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
1404         gtk_tree_selection_set_select_function(selector, prefs_filtering_selected,
1405                                                NULL, NULL);
1406
1407         /* create the columns */
1408         prefs_filtering_create_list_view_columns(GTK_WIDGET(list_view));
1409
1410         return GTK_WIDGET(list_view);
1411 }
1412
1413 static void prefs_filtering_enable_toggled(GtkCellRendererToggle *widget,
1414                 gchar *path,
1415                 GtkWidget *list_view)
1416 {
1417         GtkTreeIter iter;
1418         GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list_view));
1419         gboolean enabled = TRUE;
1420
1421         if (!gtk_tree_model_get_iter_from_string(model, &iter, path))
1422                 return;
1423
1424         gtk_tree_model_get(model, &iter,
1425                            PREFS_FILTERING_ENABLED, &enabled,
1426                            -1);
1427
1428         gtk_list_store_set(GTK_LIST_STORE(model), &iter,
1429                            PREFS_FILTERING_ENABLED, !enabled,
1430                            -1);
1431 }
1432
1433 static void prefs_filtering_create_list_view_columns(GtkWidget *list_view)
1434 {
1435         GtkTreeViewColumn *column;
1436         GtkCellRenderer *renderer;
1437
1438         renderer = gtk_cell_renderer_toggle_new();
1439         g_object_set(renderer,
1440                      "radio", FALSE,
1441                      "activatable", TRUE,
1442                      NULL);
1443         column = gtk_tree_view_column_new_with_attributes
1444                 (_("Enable"), /* FIXME : Enable, Enabled, or 'E' ? */
1445                  renderer,
1446                  "active", PREFS_FILTERING_ENABLED,
1447                  NULL);
1448         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1449         g_signal_connect(G_OBJECT(renderer), "toggled",
1450                          G_CALLBACK(prefs_filtering_enable_toggled),
1451                          list_view);
1452
1453         renderer = gtk_cell_renderer_text_new();
1454         column = gtk_tree_view_column_new_with_attributes
1455                 (_("Name"),
1456                  renderer,
1457                  "text", PREFS_FILTERING_NAME,
1458                  NULL);
1459         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1460         gtk_tree_view_column_set_resizable(column, TRUE);
1461
1462         renderer = gtk_cell_renderer_text_new();
1463         column = gtk_tree_view_column_new_with_attributes
1464                 (_("Account"),
1465                  renderer,
1466                  "text", PREFS_FILTERING_ACCOUNT_NAME,
1467                  NULL);
1468         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1469         gtk_tree_view_column_set_resizable(column, TRUE);
1470
1471         filtering.account_name_column = column;
1472                 
1473         renderer = gtk_cell_renderer_text_new();
1474         column = gtk_tree_view_column_new_with_attributes
1475                 (_("Rule"),
1476                  renderer,
1477                  "text", PREFS_FILTERING_RULE,
1478                  NULL);
1479         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
1480 }
1481
1482 static gboolean gtkut_tree_iter_comp(GtkTreeModel *model, 
1483                                      GtkTreeIter *iter1, 
1484                                      GtkTreeIter *iter2)
1485 {
1486         GtkTreePath *path1 = gtk_tree_model_get_path(model, iter1);
1487         GtkTreePath *path2 = gtk_tree_model_get_path(model, iter2);
1488         gboolean result;
1489
1490         result = gtk_tree_path_compare(path1, path2) == 0;
1491
1492         gtk_tree_path_free(path1);
1493         gtk_tree_path_free(path2);
1494         
1495         return result;
1496 }
1497
1498 /*!
1499  *\brief        Get selected row number.
1500  */
1501 static gint prefs_filtering_get_selected_row(GtkWidget *list_view)
1502 {
1503         GtkTreeView *view = GTK_TREE_VIEW(list_view);
1504         GtkTreeModel *model = gtk_tree_view_get_model(view);
1505         int n_rows = gtk_tree_model_iter_n_children(model, NULL);
1506         GtkTreeSelection *selection;
1507         GtkTreeIter iter;
1508         int row;
1509
1510         if (n_rows == 0) 
1511                 return -1;
1512         
1513         selection = gtk_tree_view_get_selection(view);
1514         if (!gtk_tree_selection_get_selected(selection, &model, &iter))
1515                 return -1;
1516         
1517         /* get all iterators and compare them... */
1518         for (row = 0; row < n_rows; row++) {
1519                 GtkTreeIter itern;
1520
1521                 gtk_tree_model_iter_nth_child(model, &itern, NULL, row);
1522                 if (gtkut_tree_iter_comp(model, &iter, &itern))
1523                         return row;
1524         }
1525         
1526         return -1;
1527 }
1528
1529 static gboolean prefs_filtering_list_view_select_row(GtkWidget *list, gint row)
1530 {
1531         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1532         GtkTreeSelection *selection = gtk_tree_view_get_selection(list_view);
1533         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1534         GtkTreeIter iter;
1535         GtkTreePath *path;
1536
1537         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1538                 return FALSE;
1539         
1540         gtk_tree_selection_select_iter(selection, &iter);
1541
1542         path = gtk_tree_model_get_path(model, &iter);
1543         gtk_tree_view_set_cursor(list_view, path, NULL, FALSE);
1544         gtk_tree_path_free(path);
1545         
1546         return TRUE;
1547 }
1548
1549 /*!
1550  *\brief        Triggered when a row is selected
1551  */
1552 static gboolean prefs_filtering_selected(GtkTreeSelection *selector,
1553                                          GtkTreeModel *model, 
1554                                          GtkTreePath *path,
1555                                          gboolean currently_selected,
1556                                          gpointer data)
1557 {
1558         if (currently_selected)
1559                 return TRUE;
1560         else {          
1561                 gboolean has_prop  = FALSE;
1562                 GtkTreeIter iter;
1563
1564                 gtk_tree_model_get_iter(model, &iter, path);
1565                 gtk_tree_model_get(model, &iter,
1566                                    PREFS_FILTERING_PROP, &has_prop,
1567                                    -1);
1568
1569                 if (has_prop) {
1570                         FilteringProp *prop;
1571                         gchar *filtering_str = NULL;
1572                         gchar *name = NULL;
1573                         gint account_id = 0;
1574
1575                         gtk_tree_model_get(model, &iter,
1576                                            PREFS_FILTERING_RULE, &filtering_str,
1577                                            -1);
1578                         gtk_tree_model_get(model, &iter,
1579                                            PREFS_FILTERING_NAME, &name,
1580                                            -1);
1581                         gtk_tree_model_get(model, &iter,
1582                                            PREFS_FILTERING_ACCOUNT_ID, &account_id,
1583                                            -1);
1584
1585                         prop = matcher_parser_get_filtering(filtering_str);
1586                         if (prop) { 
1587                                 prop->name = g_strdup(name);
1588                                 prop->account_id = account_id;
1589                                 prefs_filtering_select_set(prop);
1590                                 filteringprop_free(prop);
1591                         }                               
1592                         g_free(name);
1593                         g_free(filtering_str);
1594                 } else
1595                         prefs_filtering_reset_dialog();
1596         }               
1597
1598         return TRUE;
1599 }
1600