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