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