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