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