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