uninitialized filter list when deleting a folder; debug_print some warning if destfol...
[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_DIALOG);
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         gtk_signal_connect (GTK_OBJECT(window), "delete_event",
209                             GTK_SIGNAL_FUNC(prefs_filtering_deleted), NULL);
210         gtk_signal_connect (GTK_OBJECT(window), "key_press_event",
211                             GTK_SIGNAL_FUNC(prefs_filtering_key_pressed), NULL);
212         MANAGE_WINDOW_SIGNALS_CONNECT (window);
213         gtk_signal_connect (GTK_OBJECT(ok_btn), "clicked",
214                             GTK_SIGNAL_FUNC(prefs_filtering_ok), NULL);
215         gtk_signal_connect (GTK_OBJECT(cancel_btn), "clicked",
216                             GTK_SIGNAL_FUNC(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         gtk_signal_connect (GTK_OBJECT (cond_btn), "clicked",
241                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (action_btn), "clicked",
262                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (reg_btn), "clicked",
284                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (subst_btn), "clicked",
290                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (del_btn), "clicked",
297                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (cond_clist), "select_row",
322                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (top_btn), "clicked",
332                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (up_btn), "clicked",
340                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (down_btn), "clicked",
346                             GTK_SIGNAL_FUNC (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         gtk_signal_connect (GTK_OBJECT (bottom_btn), "clicked",
354                             GTK_SIGNAL_FUNC (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         FolderItem *item;
409         GSList * action_cur;
410         GSList * cur;
411
412         oldpathlen = strlen(old_path);
413         old_path_with_sep = g_strconcat(old_path,G_DIR_SEPARATOR_S,NULL);
414
415         for (cur = filters; cur != NULL; cur = cur->next) {
416                 FilteringProp   *filtering = (FilteringProp *)cur->data;
417                 
418                 for(action_cur = filtering->action_list ; action_cur != NULL ;
419                     action_cur = action_cur->next) {
420
421                         FilteringAction *action = action_cur->data;
422                         
423                         if (!action->destination) continue;
424                         
425                         destlen = strlen(action->destination);
426                         
427                         if (destlen > oldpathlen) {
428                                 prefixlen = destlen - oldpathlen;
429                                 suffix = action->destination + prefixlen;
430                                 
431                                 if (!strncmp(old_path, suffix, oldpathlen)) {
432                                         prefix = g_malloc0(prefixlen + 1);
433                                         strncpy2(prefix, action->destination, prefixlen);
434                                         
435                                         base = suffix + oldpathlen;
436                                         while (*base == G_DIR_SEPARATOR) base++;
437                                         if (*base == '\0')
438                                                 dest_path = g_strconcat(prefix,
439                                                     G_DIR_SEPARATOR_S,
440                                                     new_path, NULL);
441                                         else
442                                                 dest_path = g_strconcat(prefix,
443                                                     G_DIR_SEPARATOR_S,
444                                                     new_path,
445                                                     G_DIR_SEPARATOR_S,
446                                                     base, NULL);
447                                         
448                                         g_free(prefix);
449                                         g_free(action->destination);
450                                         action->destination = dest_path;
451                                 } else { /* for non-leaf folders */
452                                         /* compare with trailing slash */
453                                         if (!strncmp(old_path_with_sep, action->destination, oldpathlen+1)) {
454                                                 
455                                                 suffix = action->destination + oldpathlen + 1;
456                                                 dest_path = g_strconcat(new_path,
457                                                     G_DIR_SEPARATOR_S,
458                                                     suffix, NULL);
459                                                 g_free(action->destination);
460                                                 action->destination = dest_path;
461                                         }
462                                 }
463                         } else {
464                                 /* folder-moving a leaf */
465                                 if (!strcmp(old_path, action->destination)) {
466                                         dest_path = g_strdup(new_path);
467                                         g_free(action->destination);
468                                         action->destination = dest_path;
469                                 }
470                         }
471                 }
472         }
473 }
474
475 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data)
476 {
477         GSList *filters;
478         const gchar * old_path;
479         const gchar * new_path;
480         const gchar ** paths;
481         FolderItem *item;
482         
483         paths = data;
484         old_path = paths[0];
485         new_path = paths[1];
486
487         g_return_val_if_fail(old_path != NULL, FALSE);
488         g_return_val_if_fail(new_path != NULL, FALSE);
489         g_return_val_if_fail(node != NULL, FALSE);
490
491         item = node->data;
492         if (!item || !item->prefs)
493                 return FALSE;
494         filters = item->prefs->processing;
495
496         rename_path(filters, old_path, new_path);
497
498         return FALSE;
499 }
500
501 void prefs_filtering_delete_path(const gchar *path)
502 {
503         GList * cur;
504         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
505                 Folder *folder;
506                 folder = (Folder *) cur->data;
507                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
508                                 prefs_filtering_delete_path_func, (gchar *)path);
509         }
510         delete_path(&pre_global_processing, path);
511         delete_path(&post_global_processing, path);
512         delete_path(&filtering_rules, path);
513         
514         prefs_matcher_write_config();
515 }
516
517 static void delete_path(GSList ** p_filters, const gchar * path)
518 {
519         GSList * filters;
520         GSList * duplist;
521         gchar *suffix;
522         gint destlen;
523         gint prefixlen;
524         gint pathlen;
525         FolderItem *item;
526         GSList * action_cur;
527         GSList * cur;
528         
529         filters = *p_filters;
530         pathlen = strlen(path);
531         duplist = g_slist_copy(filters);
532         for (cur = duplist ; cur != NULL; cur = g_slist_next(cur)) {
533                 FilteringProp *filtering = (FilteringProp *) cur->data;
534                 
535                 for(action_cur = filtering->action_list ; action_cur != NULL ;
536                     action_cur = action_cur->next) {
537                 
538                         FilteringAction *action;
539                         
540                         action = action_cur->data;
541                         
542                         if (!action->destination) continue;
543                         
544                         destlen = strlen(action->destination);
545                         
546                         if (destlen > pathlen) {
547                                 prefixlen = destlen - pathlen;
548                                 suffix = action->destination + prefixlen;
549                                 
550                                 if (suffix && !strncmp(path, suffix, pathlen)) {
551                                         filteringprop_free(filtering);
552                                         filters = g_slist_remove(filters, filtering);
553                                 }
554                         } else if (strcmp(action->destination, path) == 0) {
555                                 filteringprop_free(filtering);
556                                 filters = g_slist_remove(filters, filtering);
557                         }
558                 }
559         }                
560         g_slist_free(duplist);
561         
562         * p_filters = filters;
563 }
564
565 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data)
566 {
567         const gchar *path = data;
568         FolderItem *item;
569         GSList ** p_filters;
570         
571         g_return_val_if_fail(path != NULL, FALSE);
572         g_return_val_if_fail(node != NULL, FALSE);
573
574         item = node->data;
575         if (!item || !item->prefs)
576                 return FALSE;
577         p_filters = &item->prefs->processing;
578         
579         delete_path(p_filters, path);
580
581         return FALSE;
582 }
583
584 static void prefs_filtering_set_dialog(const gchar *header, const gchar *key)
585 {
586         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
587         GSList *cur;
588         GSList * prefs_filtering;
589         gchar *cond_str[1];
590         gint row;
591         
592         gtk_clist_freeze(clist);
593         gtk_clist_clear(clist);
594
595         cond_str[0] = _("(New)");
596         row = gtk_clist_append(clist, cond_str);
597         gtk_clist_set_row_data(clist, row, NULL);
598
599         prefs_filtering = * p_processing_list;
600
601         for(cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur)) {
602                 FilteringProp * prop = (FilteringProp *) cur->data;
603
604                 cond_str[0] = filteringprop_to_string(prop);
605                 subst_char(cond_str[0], '\t', ':');
606                 row = gtk_clist_append(clist, cond_str);
607                 gtk_clist_set_row_data(clist, row, prop);
608
609                 g_free(cond_str[0]);
610         }
611
612         prefs_filtering_update_hscrollbar();
613         gtk_clist_thaw(clist);
614
615         prefs_filtering_reset_dialog();
616
617         if (header && key) {
618                 gchar * quoted_key;
619                 gchar *match_str;
620
621                 quoted_key = matcher_quote_str(key);
622                 
623                 match_str = g_strconcat(header, " ", get_matchparser_tab_str(MATCHTYPE_MATCHCASE),
624                                         " \"", quoted_key, "\"", NULL);
625                 g_free(quoted_key);
626                 
627                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), match_str);
628                 g_free(match_str);
629         }
630 }
631
632 static void prefs_filtering_reset_dialog(void)
633 {
634         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), "");
635         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), "");
636 }
637
638 static void prefs_filtering_set_list(void)
639 {
640         gint row = 1;
641         FilteringProp *prop;
642         GSList * cur;
643         gchar * filtering_str;
644         GSList * prefs_filtering;
645
646         prefs_filtering = * p_processing_list;
647
648         for(cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur))
649                 filteringprop_free((FilteringProp *) cur->data);
650         g_slist_free(prefs_filtering);
651         prefs_filtering = NULL;
652
653         while (gtk_clist_get_text(GTK_CLIST(filtering.cond_clist),
654                                   row, 0, &filtering_str)) {
655                 if (strcmp(filtering_str, _("(New)")) != 0) {
656                         prop = matcher_parser_get_filtering(filtering_str);
657                         if (prop != NULL)
658                                 prefs_filtering =
659                                         g_slist_append(prefs_filtering, prop);
660                 }
661                 row++;
662         }
663
664         * p_processing_list = prefs_filtering;
665 }
666
667 static gint prefs_filtering_clist_set_row(gint row, FilteringProp * prop)
668 {
669         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
670         gchar * str;
671         gchar *cond_str[1];
672
673         if (prop == NULL) {
674                 cond_str[0] = _("(New)");
675                 return gtk_clist_append(clist, cond_str);
676         }
677
678         str = filteringprop_to_string(prop);
679         if (str == NULL) {
680                 return -1;
681         }
682         cond_str[0] = str;
683
684         if (row < 0)
685                 row = gtk_clist_append(clist, cond_str);
686         else
687                 gtk_clist_set_text(clist, row, 0, cond_str[0]);
688         g_free(str);
689
690         return row;
691 }
692
693 static void prefs_filtering_condition_define_done(MatcherList * matchers)
694 {
695         gchar * str;
696
697         if (matchers == NULL)
698                 return;
699
700         str = matcherlist_to_string(matchers);
701
702         if (str != NULL) {
703                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), str);
704                 g_free(str);
705         }
706 }
707
708 static void prefs_filtering_condition_define(void)
709 {
710         gchar * cond_str;
711         MatcherList * matchers = NULL;
712
713         cond_str = gtk_entry_get_text(GTK_ENTRY(filtering.cond_entry));
714
715         if (*cond_str != '\0') {
716                 matchers = matcher_parser_get_cond(cond_str);
717                 if (matchers == NULL)
718                         alertpanel_error(_("Condition string is not valid."));
719         }
720
721         prefs_matcher_open(matchers, prefs_filtering_condition_define_done);
722
723         if (matchers != NULL)
724                 matcherlist_free(matchers);
725 }
726
727 static void prefs_filtering_action_define_done(GSList * action_list)
728 {
729         gchar * str;
730
731         if (action_list == NULL)
732                 return;
733
734         str = filteringaction_list_to_string(action_list);
735
736         if (str != NULL) {
737                 gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), str);
738                 g_free(str);
739         }
740 }
741
742 static void prefs_filtering_action_define(void)
743 {
744         gchar * action_str;
745         GSList * action_list = NULL;
746
747         action_str = gtk_entry_get_text(GTK_ENTRY(filtering.action_entry));
748
749         if (*action_str != '\0') {
750                 action_list = matcher_parser_get_action_list(action_str);
751                 if (action_list == NULL)
752                         alertpanel_error(_("Action string is not valid."));
753         }
754
755         prefs_filtering_action_open(action_list,
756             prefs_filtering_action_define_done);
757
758         if (action_list != NULL) {
759                 GSList * cur;
760                 for(cur = action_list ; cur != NULL ; cur = cur->next) {
761                         filteringaction_free(cur->data);
762                 }
763         }
764 }
765
766
767 /* register / substitute delete buttons */
768
769
770 static FilteringProp * prefs_filtering_dialog_to_filtering(gboolean alert)
771 {
772         MatcherList * cond;
773         gchar * cond_str;
774         gchar * action_str;
775         FilteringProp * prop;
776         GSList * action_list;
777
778         cond_str = gtk_entry_get_text(GTK_ENTRY(filtering.cond_entry));
779         if (*cond_str == '\0') {
780                 if(alert == TRUE) alertpanel_error(_("Condition string is empty."));
781                 return NULL;
782         }
783
784         action_str = gtk_entry_get_text(GTK_ENTRY(filtering.action_entry));
785         if (*action_str == '\0') {
786                 if(alert == TRUE) alertpanel_error(_("Action string is empty."));
787                 return NULL;
788         }
789
790         cond = matcher_parser_get_cond(cond_str);
791
792         if (cond == NULL) {
793                 if(alert == TRUE) alertpanel_error(_("Condition string is not valid."));
794                 return NULL;
795         }
796         
797         action_list = matcher_parser_get_action_list(action_str);
798
799         if (action_list == NULL) {
800                 if(alert == TRUE) alertpanel_error(_("Action string is not valid."));
801                 return NULL;
802         }
803
804         prop = filteringprop_new(cond, action_list);
805
806         return prop;
807 }
808
809 static void prefs_filtering_register_cb(void)
810 {
811         FilteringProp * prop;
812         
813         prop = prefs_filtering_dialog_to_filtering(TRUE);
814         if (prop == NULL)
815                 return;
816         prefs_filtering_clist_set_row(-1, prop);
817
818         filteringprop_free(prop);
819         
820         prefs_filtering_update_hscrollbar();
821 }
822
823 static void prefs_filtering_substitute_cb(void)
824 {
825         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
826         gint row;
827         FilteringProp * prop;
828         
829         if (!clist->selection) return;
830
831         row = GPOINTER_TO_INT(clist->selection->data);
832         if (row == 0) return;
833
834         prop = prefs_filtering_dialog_to_filtering(TRUE);
835         if (prop == NULL)
836                 return;
837         prefs_filtering_clist_set_row(row, prop);
838
839         filteringprop_free(prop);
840         
841         prefs_filtering_update_hscrollbar();
842 }
843
844 static void prefs_filtering_delete_cb(void)
845 {
846         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
847         gint row;
848
849         if (!clist->selection) return;
850         row = GPOINTER_TO_INT(clist->selection->data);
851         if (row == 0) return;
852
853         if (alertpanel(_("Delete rule"),
854                        _("Do you really want to delete this rule?"),
855                        _("Yes"), _("No"), NULL) == G_ALERTALTERNATE)
856                 return;
857
858         gtk_clist_remove(clist, row);
859
860         prefs_filtering_reset_dialog();
861
862         prefs_filtering_update_hscrollbar();
863 }
864
865 static void prefs_filtering_top(void)
866 {
867         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
868         gint row;
869
870         if (!clist->selection) return;
871
872         row = GPOINTER_TO_INT(clist->selection->data);
873         if (row > 1)
874                 gtk_clist_row_move(clist, row, 1);
875 }
876
877 static void prefs_filtering_up(void)
878 {
879         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
880         gint row;
881
882         if (!clist->selection) return;
883
884         row = GPOINTER_TO_INT(clist->selection->data);
885         if (row > 1) {
886                 gtk_clist_row_move(clist, row, row - 1);
887                 if (gtk_clist_row_is_visible(clist, row - 1) != GTK_VISIBILITY_FULL) 
888                         gtk_clist_moveto(clist, row - 1, 0, 0, 0); 
889         }
890 }
891
892 static void prefs_filtering_down(void)
893 {
894         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
895         gint row;
896
897         if (!clist->selection) return;
898
899         row = GPOINTER_TO_INT(clist->selection->data);
900         if (row > 0 && row < clist->rows - 1) {
901                 gtk_clist_row_move(clist, row, row + 1);
902                 if (gtk_clist_row_is_visible(clist, row + 1) != GTK_VISIBILITY_FULL)
903                         gtk_clist_moveto(clist, row + 1, 0, 1, 0); 
904         }
905 }
906
907 static void prefs_filtering_bottom(void)
908 {
909         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
910         gint row;
911
912         if (!clist->selection) return;
913
914         row = GPOINTER_TO_INT(clist->selection->data);
915         if (row > 0 && row < clist->rows - 1)
916                 gtk_clist_row_move(clist, row, clist->rows - 1);
917 }
918
919 static void prefs_filtering_select_set(FilteringProp *prop)
920 {
921         gchar *matcher_str;
922         gchar *action_str;
923
924         prefs_filtering_reset_dialog();
925
926         matcher_str = matcherlist_to_string(prop->matchers);
927         if (matcher_str == NULL) {
928                 return;
929         }
930
931         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), matcher_str);
932
933         action_str = filteringaction_list_to_string(prop->action_list);
934         if (matcher_str == NULL) {
935                 return;
936         }
937         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), action_str);
938
939         g_free(action_str);
940         g_free(matcher_str);
941 }
942
943 static void prefs_filtering_select(GtkCList *clist, gint row, gint column,
944                                 GdkEvent *event)
945 {
946         FilteringProp * prop;
947         gchar * filtering_str;
948
949         if (row == 0) {
950                 prefs_filtering_reset_dialog();
951                 return;
952         }
953
954         if (!gtk_clist_get_text(GTK_CLIST(filtering.cond_clist),
955                                 row, 0, &filtering_str))
956                 return;
957         
958         prop = matcher_parser_get_filtering(filtering_str);
959         if (prop == NULL)
960                 return;
961
962         prefs_filtering_select_set(prop);
963
964         filteringprop_free(prop);
965 }
966
967
968 static gint prefs_filtering_deleted(GtkWidget *widget, GdkEventAny *event,
969                                  gpointer data)
970 {
971         prefs_filtering_cancel();
972         return TRUE;
973 }
974
975 static gboolean prefs_filtering_key_pressed(GtkWidget *widget, GdkEventKey *event,
976                                      gpointer data)
977 {
978         if (event && event->keyval == GDK_Escape)
979                 prefs_filtering_cancel();
980         return TRUE;                    
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 }