This commit was manufactured by cvs2svn to create branch 'gtk2'.
[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 <gtk/gtk.h>
28 #include <gtk/gtkoptionmenu.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34
35 #include "intl.h"
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 static struct Filtering {
58         GtkWidget *window;
59
60         GtkWidget *ok_btn;
61         GtkWidget *cond_entry;
62         GtkWidget *action_entry;
63
64         GtkWidget *cond_clist;
65 } filtering;
66
67 static GSList ** p_processing_list = NULL;
68
69 /* widget creating functions */
70 static void prefs_filtering_create              (void);
71
72 static void prefs_filtering_set_dialog  (const gchar *header,
73                                          const gchar *key);
74 static void prefs_filtering_set_list    (void);
75
76 /* callback functions */
77 static void prefs_filtering_register_cb (void);
78 static void prefs_filtering_substitute_cb       (void);
79 static void prefs_filtering_delete_cb   (void);
80 static void prefs_filtering_top         (void);
81 static void prefs_filtering_up          (void);
82 static void prefs_filtering_down        (void);
83 static void prefs_filtering_bottom      (void);
84 static void prefs_filtering_select      (GtkCList       *clist,
85                                          gint            row,
86                                          gint            column,
87                                          GdkEvent       *event);
88
89 static gint prefs_filtering_deleted     (GtkWidget      *widget,
90                                          GdkEventAny    *event,
91                                          gpointer        data);
92 static gboolean prefs_filtering_key_pressed(GtkWidget   *widget,
93                                          GdkEventKey    *event,
94                                          gpointer        data);
95 static void prefs_filtering_cancel      (void);
96 static void prefs_filtering_ok          (void);
97
98 static void prefs_filtering_condition_define    (void);
99 static void prefs_filtering_action_define(void);
100 static gint prefs_filtering_clist_set_row       (gint row, FilteringProp * prop);
101                                           
102 static void prefs_filtering_reset_dialog        (void);
103 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data);
104 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data);
105
106 static void delete_path(GSList ** p_filters, const gchar * path);
107
108 void prefs_filtering_open(GSList ** p_processing,
109                           const gchar * title,
110                           const gchar *header,
111                           const gchar *key)
112 {
113         if (prefs_rc_is_readonly(FILTERING_RC))
114                 return;
115
116         inc_lock();
117
118         if (!filtering.window) {
119                 prefs_filtering_create();
120         }
121
122         manage_window_set_transient(GTK_WINDOW(filtering.window));
123         gtk_widget_grab_focus(filtering.ok_btn);
124         
125         if (title != NULL)
126                 gtk_window_set_title(GTK_WINDOW(filtering.window), title);
127         else
128                 gtk_window_set_title (GTK_WINDOW(filtering.window),
129                                       _("Filtering/Processing configuration"));
130         
131         p_processing_list = p_processing;
132         
133         prefs_filtering_set_dialog(header, key);
134
135         gtk_widget_show(filtering.window);
136
137         start_address_completion();
138 }
139
140 /* prefs_filtering_close() - just to have one common exit point */
141 static void prefs_filtering_close(void)
142 {
143         end_address_completion();
144         
145         gtk_widget_hide(filtering.window);
146         inc_unlock();
147 }
148
149 static void prefs_filtering_create(void)
150 {
151         GtkWidget *window;
152         GtkWidget *vbox;
153         GtkWidget *ok_btn;
154         GtkWidget *cancel_btn;
155         GtkWidget *confirm_area;
156
157         GtkWidget *vbox1;
158         GtkWidget *hbox1;
159         GtkWidget *reg_hbox;
160         GtkWidget *arrow;
161         GtkWidget *btn_hbox;
162
163         GtkWidget *cond_label;
164         GtkWidget *cond_entry;
165         GtkWidget *cond_btn;
166         GtkWidget *action_label;
167         GtkWidget *action_entry;
168         GtkWidget *action_btn;
169
170         GtkWidget *reg_btn;
171         GtkWidget *subst_btn;
172         GtkWidget *del_btn;
173
174         GtkWidget *cond_hbox;
175         GtkWidget *cond_scrolledwin;
176         GtkWidget *cond_clist;
177
178         GtkWidget *btn_vbox;
179         GtkWidget *spc_vbox;
180         GtkWidget *top_btn;
181         GtkWidget *up_btn;
182         GtkWidget *down_btn;
183         GtkWidget *bottom_btn;
184
185         gchar *title[1];
186
187         debug_print("Creating filtering configuration window...\n");
188
189         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
190         gtk_container_set_border_width (GTK_CONTAINER (window), 8);
191         gtk_window_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
192         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
193         gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, FALSE);
194
195         vbox = gtk_vbox_new (FALSE, 6);
196         gtk_widget_show (vbox);
197         gtk_container_add (GTK_CONTAINER (window), vbox);
198
199         gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"),
200                                 &cancel_btn, _("Cancel"), NULL, NULL);
201         gtk_widget_show (confirm_area);
202         gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
203         gtk_widget_grab_default (ok_btn);
204
205         gtk_window_set_title (GTK_WINDOW(window),
206                                     _("Filtering/Processing configuration"));
207
208         g_signal_connect(G_OBJECT(window), "delete_event",
209                          G_CALLBACK(prefs_filtering_deleted), NULL);
210         g_signal_connect(G_OBJECT(window), "key_press_event",
211                          G_CALLBACK(prefs_filtering_key_pressed), NULL);
212         MANAGE_WINDOW_SIGNALS_CONNECT (window);
213         g_signal_connect(G_OBJECT(ok_btn), "clicked",
214                          G_CALLBACK(prefs_filtering_ok), NULL);
215         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
216                          G_CALLBACK(prefs_filtering_cancel), NULL);
217
218         vbox1 = gtk_vbox_new (FALSE, VSPACING);
219         gtk_widget_show (vbox1);
220         gtk_box_pack_start (GTK_BOX (vbox), vbox1, TRUE, TRUE, 0);
221         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
222
223         cond_label = gtk_label_new (_("Condition"));
224         gtk_widget_show (cond_label);
225         gtk_misc_set_alignment (GTK_MISC (cond_label), 0, 0.5);
226         gtk_box_pack_start (GTK_BOX (vbox1), cond_label, FALSE, FALSE, 0);
227
228         hbox1 = gtk_hbox_new (FALSE, VSPACING);
229         gtk_widget_show (hbox1);
230         gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0);
231         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
232
233         cond_entry = gtk_entry_new ();
234         gtk_widget_show (cond_entry);
235         gtk_box_pack_start (GTK_BOX (hbox1), cond_entry, TRUE, TRUE, 0);
236
237         cond_btn = gtk_button_new_with_label (_("Define ..."));
238         gtk_widget_show (cond_btn);
239         gtk_box_pack_start (GTK_BOX (hbox1), cond_btn, FALSE, FALSE, 0);
240         g_signal_connect(G_OBJECT (cond_btn), "clicked",
241                          G_CALLBACK(prefs_filtering_condition_define),
242                          NULL);
243
244         action_label = gtk_label_new (_("Action"));
245         gtk_widget_show (action_label);
246         gtk_misc_set_alignment (GTK_MISC (action_label), 0, 0.5);
247         gtk_box_pack_start (GTK_BOX (vbox1), action_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         action_entry = gtk_entry_new ();
255         gtk_widget_show (action_entry);
256         gtk_box_pack_start (GTK_BOX (hbox1), action_entry, TRUE, TRUE, 0);
257
258         action_btn = gtk_button_new_with_label (_("Define ..."));
259         gtk_widget_show (action_btn);
260         gtk_box_pack_start (GTK_BOX (hbox1), action_btn, FALSE, FALSE, 0);
261         g_signal_connect(G_OBJECT (action_btn), "clicked",
262                          G_CALLBACK(prefs_filtering_action_define),
263                          NULL);
264
265         /* register / substitute / delete */
266
267         reg_hbox = gtk_hbox_new (FALSE, 4);
268         gtk_widget_show (reg_hbox);
269         gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox, FALSE, FALSE, 0);
270
271         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
272         gtk_widget_show (arrow);
273         gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0);
274         gtk_widget_set_usize (arrow, -1, 16);
275
276         btn_hbox = gtk_hbox_new (TRUE, 4);
277         gtk_widget_show (btn_hbox);
278         gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0);
279
280         reg_btn = gtk_button_new_with_label (_("Add"));
281         gtk_widget_show (reg_btn);
282         gtk_box_pack_start (GTK_BOX (btn_hbox), reg_btn, FALSE, TRUE, 0);
283         g_signal_connect(G_OBJECT (reg_btn), "clicked",
284                          G_CALLBACK(prefs_filtering_register_cb), NULL);
285
286         subst_btn = gtk_button_new_with_label (_("  Replace  "));
287         gtk_widget_show (subst_btn);
288         gtk_box_pack_start (GTK_BOX (btn_hbox), subst_btn, FALSE, TRUE, 0);
289         g_signal_connect(G_OBJECT (subst_btn), "clicked",
290                          G_CALLBACK(prefs_filtering_substitute_cb),
291                          NULL);
292
293         del_btn = gtk_button_new_with_label (_("Delete"));
294         gtk_widget_show (del_btn);
295         gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0);
296         g_signal_connect(G_OBJECT (del_btn), "clicked",
297                         G_CALLBACK(prefs_filtering_delete_cb), NULL);
298
299         cond_hbox = gtk_hbox_new (FALSE, 8);
300         gtk_widget_show (cond_hbox);
301         gtk_box_pack_start (GTK_BOX (vbox1), cond_hbox, TRUE, TRUE, 0);
302
303         cond_scrolledwin = gtk_scrolled_window_new (NULL, NULL);
304         gtk_widget_show (cond_scrolledwin);
305         gtk_widget_set_usize (cond_scrolledwin, -1, 150);
306         gtk_box_pack_start (GTK_BOX (cond_hbox), cond_scrolledwin,
307                             TRUE, TRUE, 0);
308         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cond_scrolledwin),
309                                         GTK_POLICY_AUTOMATIC,
310                                         GTK_POLICY_AUTOMATIC);
311
312         title[0] = _("Current filtering/processing rules");
313         cond_clist = gtk_clist_new_with_titles(1, title);
314         gtk_widget_show (cond_clist);
315         gtk_container_add (GTK_CONTAINER (cond_scrolledwin), cond_clist);
316         gtk_clist_set_column_width (GTK_CLIST (cond_clist), 0, 80);
317         gtk_clist_set_selection_mode (GTK_CLIST (cond_clist),
318                                       GTK_SELECTION_BROWSE);
319         GTK_WIDGET_UNSET_FLAGS (GTK_CLIST (cond_clist)->column[0].button,
320                                 GTK_CAN_FOCUS);
321         g_signal_connect(G_OBJECT (cond_clist), "select_row",
322                          G_CALLBACK(prefs_filtering_select), NULL);
323
324         btn_vbox = gtk_vbox_new (FALSE, 8);
325         gtk_widget_show (btn_vbox);
326         gtk_box_pack_start (GTK_BOX (cond_hbox), btn_vbox, FALSE, FALSE, 0);
327
328         top_btn = gtk_button_new_with_label (_("Top"));
329         gtk_widget_show (top_btn);
330         gtk_box_pack_start (GTK_BOX (btn_vbox), top_btn, FALSE, FALSE, 0);
331         g_signal_connect(G_OBJECT (top_btn), "clicked",
332                          G_CALLBACK(prefs_filtering_top), NULL);
333
334         PACK_VSPACER (btn_vbox, spc_vbox, VSPACING_NARROW_2);
335
336         up_btn = gtk_button_new_with_label (_("Up"));
337         gtk_widget_show (up_btn);
338         gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0);
339         g_signal_connect(G_OBJECT (up_btn), "clicked",
340                          G_CALLBACK(prefs_filtering_up), NULL);
341
342         down_btn = gtk_button_new_with_label (_("Down"));
343         gtk_widget_show (down_btn);
344         gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0);
345         g_signal_connect(G_OBJECT (down_btn), "clicked",
346                          G_CALLBACK(prefs_filtering_down), NULL);
347
348         PACK_VSPACER (btn_vbox, spc_vbox, VSPACING_NARROW_2);
349
350         bottom_btn = gtk_button_new_with_label (_("Bottom"));
351         gtk_widget_show (bottom_btn);
352         gtk_box_pack_start (GTK_BOX (btn_vbox), bottom_btn, FALSE, FALSE, 0);
353         g_signal_connect(G_OBJECT (bottom_btn), "clicked",
354                          G_CALLBACK(prefs_filtering_bottom), NULL);
355
356         gtk_widget_set_usize(window, 500, -1);
357
358         gtk_widget_show_all(window);
359
360         filtering.window    = window;
361         filtering.ok_btn = ok_btn;
362
363         filtering.cond_entry = cond_entry;
364         filtering.action_entry = action_entry;
365         filtering.cond_clist   = cond_clist;
366 }
367
368 static void prefs_filtering_update_hscrollbar(void)
369 {
370         gint optwidth = gtk_clist_optimal_column_width(GTK_CLIST(filtering.cond_clist), 0);
371         gtk_clist_set_column_width(GTK_CLIST(filtering.cond_clist), 0, optwidth);
372 }
373
374 static void rename_path(GSList * filters,
375                         const gchar * old_path, const gchar * new_path);
376
377 void prefs_filtering_rename_path(const gchar *old_path, const gchar *new_path)
378 {
379         GList * cur;
380         const gchar *paths[2] = {NULL, NULL};
381         paths[0] = old_path;
382         paths[1] = new_path;
383         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
384                 Folder *folder;
385                 folder = (Folder *) cur->data;
386                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
387                                 prefs_filtering_rename_path_func, paths);
388         }
389         
390         rename_path(pre_global_processing, old_path, new_path);
391         rename_path(post_global_processing, old_path, new_path);
392         rename_path(filtering_rules, old_path, new_path);
393         
394         prefs_matcher_write_config();
395 }
396
397 static void rename_path(GSList * filters,
398                         const gchar * old_path, const gchar * new_path)
399 {
400         gchar *base;
401         gchar *prefix;
402         gchar *suffix;
403         gchar *dest_path;
404         gchar *old_path_with_sep;
405         gint destlen;
406         gint prefixlen;
407         gint oldpathlen;
408         GSList * action_cur;
409         GSList * cur;
410
411         oldpathlen = strlen(old_path);
412         old_path_with_sep = g_strconcat(old_path,G_DIR_SEPARATOR_S,NULL);
413
414         for (cur = filters; cur != NULL; cur = cur->next) {
415                 FilteringProp   *filtering = (FilteringProp *)cur->data;
416                 
417                 for(action_cur = filtering->action_list ; action_cur != NULL ;
418                     action_cur = action_cur->next) {
419
420                         FilteringAction *action = action_cur->data;
421                         
422                         if (!action->destination) continue;
423                         
424                         destlen = strlen(action->destination);
425                         
426                         if (destlen > oldpathlen) {
427                                 prefixlen = destlen - oldpathlen;
428                                 suffix = action->destination + prefixlen;
429                                 
430                                 if (!strncmp(old_path, suffix, oldpathlen)) {
431                                         prefix = g_malloc0(prefixlen + 1);
432                                         strncpy2(prefix, action->destination, prefixlen);
433                                         
434                                         base = suffix + oldpathlen;
435                                         while (*base == G_DIR_SEPARATOR) base++;
436                                         if (*base == '\0')
437                                                 dest_path = g_strconcat(prefix,
438                                                     G_DIR_SEPARATOR_S,
439                                                     new_path, NULL);
440                                         else
441                                                 dest_path = g_strconcat(prefix,
442                                                     G_DIR_SEPARATOR_S,
443                                                     new_path,
444                                                     G_DIR_SEPARATOR_S,
445                                                     base, NULL);
446                                         
447                                         g_free(prefix);
448                                         g_free(action->destination);
449                                         action->destination = dest_path;
450                                 } else { /* for non-leaf folders */
451                                         /* compare with trailing slash */
452                                         if (!strncmp(old_path_with_sep, action->destination, oldpathlen+1)) {
453                                                 
454                                                 suffix = action->destination + oldpathlen + 1;
455                                                 dest_path = g_strconcat(new_path,
456                                                     G_DIR_SEPARATOR_S,
457                                                     suffix, NULL);
458                                                 g_free(action->destination);
459                                                 action->destination = dest_path;
460                                         }
461                                 }
462                         } else {
463                                 /* folder-moving a leaf */
464                                 if (!strcmp(old_path, action->destination)) {
465                                         dest_path = g_strdup(new_path);
466                                         g_free(action->destination);
467                                         action->destination = dest_path;
468                                 }
469                         }
470                 }
471         }
472 }
473
474 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data)
475 {
476         GSList *filters;
477         const gchar * old_path;
478         const gchar * new_path;
479         const gchar ** paths;
480         FolderItem *item;
481         
482         paths = data;
483         old_path = paths[0];
484         new_path = paths[1];
485
486         g_return_val_if_fail(old_path != NULL, FALSE);
487         g_return_val_if_fail(new_path != NULL, FALSE);
488         g_return_val_if_fail(node != NULL, FALSE);
489
490         item = node->data;
491         if (!item || !item->prefs)
492                 return FALSE;
493         filters = item->prefs->processing;
494
495         rename_path(filters, old_path, new_path);
496
497         return FALSE;
498 }
499
500 void prefs_filtering_delete_path(const gchar *path)
501 {
502         GList * cur;
503         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
504                 Folder *folder;
505                 folder = (Folder *) cur->data;
506                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
507                                 prefs_filtering_delete_path_func, (gchar *)path);
508         }
509         delete_path(&pre_global_processing, path);
510         delete_path(&post_global_processing, path);
511         delete_path(&filtering_rules, path);
512         
513         prefs_matcher_write_config();
514 }
515
516 static void delete_path(GSList ** p_filters, const gchar * path)
517 {
518         GSList * filters;
519         GSList * duplist;
520         gchar *suffix;
521         gint destlen;
522         gint prefixlen;
523         gint pathlen;
524         GSList * action_cur;
525         GSList * cur;
526         
527         filters = *p_filters;
528         pathlen = strlen(path);
529         duplist = g_slist_copy(filters);
530         for (cur = duplist ; cur != NULL; cur = g_slist_next(cur)) {
531                 FilteringProp *filtering = (FilteringProp *) cur->data;
532                 
533                 for(action_cur = filtering->action_list ; action_cur != NULL ;
534                     action_cur = action_cur->next) {
535                 
536                         FilteringAction *action;
537                         
538                         action = action_cur->data;
539                         
540                         if (!action->destination) continue;
541                         
542                         destlen = strlen(action->destination);
543                         
544                         if (destlen > pathlen) {
545                                 prefixlen = destlen - pathlen;
546                                 suffix = action->destination + prefixlen;
547                                 
548                                 if (suffix && !strncmp(path, suffix, pathlen)) {
549                                         filteringprop_free(filtering);
550                                         filters = g_slist_remove(filters, filtering);
551                                 }
552                         } else if (strcmp(action->destination, path) == 0) {
553                                 filteringprop_free(filtering);
554                                 filters = g_slist_remove(filters, filtering);
555                         }
556                 }
557         }                
558         g_slist_free(duplist);
559         
560         * p_filters = filters;
561 }
562
563 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data)
564 {
565         const gchar *path = data;
566         FolderItem *item;
567         GSList ** p_filters;
568         
569         g_return_val_if_fail(path != NULL, FALSE);
570         g_return_val_if_fail(node != NULL, FALSE);
571
572         item = node->data;
573         if (!item || !item->prefs)
574                 return FALSE;
575         p_filters = &item->prefs->processing;
576         
577         delete_path(p_filters, path);
578
579         return FALSE;
580 }
581
582 static void prefs_filtering_set_dialog(const gchar *header, const gchar *key)
583 {
584         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
585         GSList *cur;
586         GSList * prefs_filtering;
587         gchar *cond_str[1];
588         gint row;
589         
590         gtk_clist_freeze(clist);
591         gtk_clist_clear(clist);
592
593         cond_str[0] = _("(New)");
594         row = gtk_clist_append(clist, cond_str);
595         gtk_clist_set_row_data(clist, row, NULL);
596
597         prefs_filtering = * p_processing_list;
598
599         for(cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur)) {
600                 FilteringProp * prop = (FilteringProp *) cur->data;
601
602                 cond_str[0] = filteringprop_to_string(prop);
603                 subst_char(cond_str[0], '\t', ':');
604                 row = gtk_clist_append(clist, cond_str);
605                 gtk_clist_set_row_data(clist, row, prop);
606
607                 g_free(cond_str[0]);
608         }
609
610         prefs_filtering_update_hscrollbar();
611         gtk_clist_thaw(clist);
612
613         prefs_filtering_reset_dialog();
614
615         if (header && key) {
616                 gchar * quoted_key;
617                 gchar *match_str;
618
619                 quoted_key = matcher_quote_str(key);
620                 
621                 match_str = g_strconcat(header, " ", get_matchparser_tab_str(MATCHTYPE_MATCHCASE),
622                                         " \"", quoted_key, "\"", NULL);
623                 g_free(quoted_key);
624                 
625                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), match_str);
626                 g_free(match_str);
627         }
628 }
629
630 static void prefs_filtering_reset_dialog(void)
631 {
632         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), "");
633         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), "");
634 }
635
636 static void prefs_filtering_set_list(void)
637 {
638         gint row = 1;
639         FilteringProp *prop;
640         GSList * cur;
641         gchar * filtering_str;
642         GSList * prefs_filtering;
643
644         prefs_filtering = * p_processing_list;
645
646         for(cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur))
647                 filteringprop_free((FilteringProp *) cur->data);
648         g_slist_free(prefs_filtering);
649         prefs_filtering = NULL;
650
651         while (gtk_clist_get_text(GTK_CLIST(filtering.cond_clist),
652                                   row, 0, &filtering_str)) {
653                 if (strcmp(filtering_str, _("(New)")) != 0) {
654                         prop = matcher_parser_get_filtering(filtering_str);
655                         if (prop != NULL)
656                                 prefs_filtering =
657                                         g_slist_append(prefs_filtering, prop);
658                 }
659                 row++;
660         }
661
662         * p_processing_list = prefs_filtering;
663 }
664
665 static gint prefs_filtering_clist_set_row(gint row, FilteringProp * prop)
666 {
667         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
668         gchar * str;
669         gchar *cond_str[1];
670
671         if (prop == NULL) {
672                 cond_str[0] = _("(New)");
673                 return gtk_clist_append(clist, cond_str);
674         }
675
676         str = filteringprop_to_string(prop);
677         if (str == NULL) {
678                 return -1;
679         }
680         cond_str[0] = str;
681
682         if (row < 0)
683                 row = gtk_clist_append(clist, cond_str);
684         else
685                 gtk_clist_set_text(clist, row, 0, cond_str[0]);
686         g_free(str);
687
688         return row;
689 }
690
691 static void prefs_filtering_condition_define_done(MatcherList * matchers)
692 {
693         gchar * str;
694
695         if (matchers == NULL)
696                 return;
697
698         str = matcherlist_to_string(matchers);
699
700         if (str != NULL) {
701                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), str);
702                 g_free(str);
703         }
704 }
705
706 static void prefs_filtering_condition_define(void)
707 {
708         const gchar * cond_str;
709         MatcherList * matchers = NULL;
710
711         cond_str = gtk_entry_get_text(GTK_ENTRY(filtering.cond_entry));
712
713         if (*cond_str != '\0') {
714                 matchers = matcher_parser_get_cond(cond_str);
715                 if (matchers == NULL)
716                         alertpanel_error(_("Condition string is not valid."));
717         }
718
719         prefs_matcher_open(matchers, prefs_filtering_condition_define_done);
720
721         if (matchers != NULL)
722                 matcherlist_free(matchers);
723 }
724
725 static void prefs_filtering_action_define_done(GSList * action_list)
726 {
727         gchar * str;
728
729         if (action_list == NULL)
730                 return;
731
732         str = filteringaction_list_to_string(action_list);
733
734         if (str != NULL) {
735                 gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), str);
736                 g_free(str);
737         }
738 }
739
740 static void prefs_filtering_action_define(void)
741 {
742         const gchar * action_str;
743         GSList * action_list = NULL;
744
745         action_str = gtk_entry_get_text(GTK_ENTRY(filtering.action_entry));
746
747         if (*action_str != '\0') {
748                 action_list = matcher_parser_get_action_list(action_str);
749                 if (action_list == NULL)
750                         alertpanel_error(_("Action string is not valid."));
751         }
752
753         prefs_filtering_action_open(action_list,
754             prefs_filtering_action_define_done);
755
756         if (action_list != NULL) {
757                 GSList * cur;
758                 for(cur = action_list ; cur != NULL ; cur = cur->next) {
759                         filteringaction_free(cur->data);
760                 }
761         }
762 }
763
764
765 /* register / substitute delete buttons */
766
767
768 static FilteringProp * prefs_filtering_dialog_to_filtering(gboolean alert)
769 {
770         MatcherList * cond;
771         const gchar * cond_str;
772         const gchar * action_str;
773         FilteringProp * prop;
774         GSList * action_list;
775
776         cond_str = gtk_entry_get_text(GTK_ENTRY(filtering.cond_entry));
777         if (*cond_str == '\0') {
778                 if(alert == TRUE) alertpanel_error(_("Condition string is empty."));
779                 return NULL;
780         }
781
782         action_str = gtk_entry_get_text(GTK_ENTRY(filtering.action_entry));
783         if (*action_str == '\0') {
784                 if(alert == TRUE) alertpanel_error(_("Action string is empty."));
785                 return NULL;
786         }
787
788         cond = matcher_parser_get_cond(cond_str);
789
790         if (cond == NULL) {
791                 if(alert == TRUE) alertpanel_error(_("Condition string is not valid."));
792                 return NULL;
793         }
794         
795         action_list = matcher_parser_get_action_list(action_str);
796
797         if (action_list == NULL) {
798                 if(alert == TRUE) alertpanel_error(_("Action string is not valid."));
799                 return NULL;
800         }
801
802         prop = filteringprop_new(cond, action_list);
803
804         return prop;
805 }
806
807 static void prefs_filtering_register_cb(void)
808 {
809         FilteringProp * prop;
810         
811         prop = prefs_filtering_dialog_to_filtering(TRUE);
812         if (prop == NULL)
813                 return;
814         prefs_filtering_clist_set_row(-1, prop);
815
816         filteringprop_free(prop);
817         
818         prefs_filtering_update_hscrollbar();
819 }
820
821 static void prefs_filtering_substitute_cb(void)
822 {
823         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
824         gint row;
825         FilteringProp * prop;
826         
827         if (!clist->selection) return;
828
829         row = GPOINTER_TO_INT(clist->selection->data);
830         if (row == 0) return;
831
832         prop = prefs_filtering_dialog_to_filtering(TRUE);
833         if (prop == NULL)
834                 return;
835         prefs_filtering_clist_set_row(row, prop);
836
837         filteringprop_free(prop);
838         
839         prefs_filtering_update_hscrollbar();
840 }
841
842 static void prefs_filtering_delete_cb(void)
843 {
844         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
845         gint row;
846
847         if (!clist->selection) return;
848         row = GPOINTER_TO_INT(clist->selection->data);
849         if (row == 0) return;
850
851         if (alertpanel(_("Delete rule"),
852                        _("Do you really want to delete this rule?"),
853                        _("Yes"), _("No"), NULL) == G_ALERTALTERNATE)
854                 return;
855
856         gtk_clist_remove(clist, row);
857
858         prefs_filtering_reset_dialog();
859
860         prefs_filtering_update_hscrollbar();
861 }
862
863 static void prefs_filtering_top(void)
864 {
865         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
866         gint row;
867
868         if (!clist->selection) return;
869
870         row = GPOINTER_TO_INT(clist->selection->data);
871         if (row > 1)
872                 gtk_clist_row_move(clist, row, 1);
873 }
874
875 static void prefs_filtering_up(void)
876 {
877         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
878         gint row;
879
880         if (!clist->selection) return;
881
882         row = GPOINTER_TO_INT(clist->selection->data);
883         if (row > 1) {
884                 gtk_clist_row_move(clist, row, row - 1);
885                 if (gtk_clist_row_is_visible(clist, row - 1) != GTK_VISIBILITY_FULL) 
886                         gtk_clist_moveto(clist, row - 1, 0, 0, 0); 
887         }
888 }
889
890 static void prefs_filtering_down(void)
891 {
892         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
893         gint row;
894
895         if (!clist->selection) return;
896
897         row = GPOINTER_TO_INT(clist->selection->data);
898         if (row > 0 && row < clist->rows - 1) {
899                 gtk_clist_row_move(clist, row, row + 1);
900                 if (gtk_clist_row_is_visible(clist, row + 1) != GTK_VISIBILITY_FULL)
901                         gtk_clist_moveto(clist, row + 1, 0, 1, 0); 
902         }
903 }
904
905 static void prefs_filtering_bottom(void)
906 {
907         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
908         gint row;
909
910         if (!clist->selection) return;
911
912         row = GPOINTER_TO_INT(clist->selection->data);
913         if (row > 0 && row < clist->rows - 1)
914                 gtk_clist_row_move(clist, row, clist->rows - 1);
915 }
916
917 static void prefs_filtering_select_set(FilteringProp *prop)
918 {
919         gchar *matcher_str;
920         gchar *action_str;
921
922         prefs_filtering_reset_dialog();
923
924         matcher_str = matcherlist_to_string(prop->matchers);
925         if (matcher_str == NULL) {
926                 return;
927         }
928
929         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), matcher_str);
930
931         action_str = filteringaction_list_to_string(prop->action_list);
932         if (matcher_str == NULL) {
933                 return;
934         }
935         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), action_str);
936
937         g_free(action_str);
938         g_free(matcher_str);
939 }
940
941 static void prefs_filtering_select(GtkCList *clist, gint row, gint column,
942                                 GdkEvent *event)
943 {
944         FilteringProp * prop;
945         gchar * filtering_str;
946
947         if (row == 0) {
948                 prefs_filtering_reset_dialog();
949                 return;
950         }
951
952         if (!gtk_clist_get_text(GTK_CLIST(filtering.cond_clist),
953                                 row, 0, &filtering_str))
954                 return;
955         
956         prop = matcher_parser_get_filtering(filtering_str);
957         if (prop == NULL)
958                 return;
959
960         prefs_filtering_select_set(prop);
961
962         filteringprop_free(prop);
963 }
964
965
966 static gint prefs_filtering_deleted(GtkWidget *widget, GdkEventAny *event,
967                                  gpointer data)
968 {
969         prefs_filtering_cancel();
970         return TRUE;
971 }
972
973 static gboolean prefs_filtering_key_pressed(GtkWidget *widget, GdkEventKey *event,
974                                      gpointer data)
975 {
976         if (event && event->keyval == GDK_Escape) {
977                 prefs_filtering_cancel();
978                 return TRUE;                    
979         }
980         return FALSE;
981 }
982
983 static void prefs_filtering_ok(void)
984 {
985         FilteringProp * prop;
986         gchar * str;
987         gchar * filtering_str;
988         gint row = 1;
989         AlertValue val;
990         
991         prop = prefs_filtering_dialog_to_filtering(FALSE);
992         if (prop != NULL) {
993                 str = filteringprop_to_string(prop);
994
995                 while (gtk_clist_get_text(GTK_CLIST(filtering.cond_clist),
996                                           row, 0, &filtering_str)) {
997                         if (strcmp(filtering_str, str) == 0) break;
998                         row++;
999                 }
1000                 if (strcmp(filtering_str, str) != 0) {
1001                         val = alertpanel(_("Entry not saved"),
1002                                  _("The entry was not saved. Close anyway?"),
1003                                  _("Yes"), _("No"), NULL);
1004                         if (G_ALERTDEFAULT != val) {
1005                                 g_free(str);
1006                                 return;
1007                         }
1008                 }
1009                 g_free(str);
1010         }
1011         prefs_filtering_set_list();
1012         prefs_matcher_write_config();
1013         prefs_filtering_close();
1014 }
1015
1016 static void prefs_filtering_cancel(void)
1017 {
1018         prefs_matcher_read_config();
1019         prefs_filtering_close();
1020 }