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