2004-12-30 [paul] 0.9.13cvs22.2
[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_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
192         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
193         gtk_window_set_resizable(GTK_WINDOW (window), TRUE);
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         gchar * cond_str;
709         MatcherList * matchers = NULL;
710
711         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
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         g_free(cond_str);
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_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
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         g_free(action_str);
756
757         prefs_filtering_action_open(action_list,
758             prefs_filtering_action_define_done);
759
760         if (action_list != NULL) {
761                 GSList * cur;
762                 for(cur = action_list ; cur != NULL ; cur = cur->next) {
763                         filteringaction_free(cur->data);
764                 }
765         }
766 }
767
768
769 /* register / substitute delete buttons */
770
771
772 static FilteringProp * prefs_filtering_dialog_to_filtering(gboolean alert)
773 {
774         MatcherList * cond;
775         gchar * cond_str = NULL;
776         gchar * action_str = NULL;
777         FilteringProp * prop = NULL;
778         GSList * action_list;
779
780         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
781         if (*cond_str == '\0') {
782                 if(alert == TRUE) alertpanel_error(_("Condition string is empty."));
783                 goto fail;
784         }
785         
786         action_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
787         if (*action_str == '\0') {
788                 if(alert == TRUE) alertpanel_error(_("Action string is empty."));
789                 goto fail;
790         }
791
792         cond = matcher_parser_get_cond(cond_str);
793
794         if (cond == NULL) {
795                 if(alert == TRUE) alertpanel_error(_("Condition string is not valid."));
796                 goto fail;
797         }
798         
799         action_list = matcher_parser_get_action_list(action_str);
800         
801
802         if (action_list == NULL) {
803                 if(alert == TRUE) alertpanel_error(_("Action string is not valid."));
804                 goto fail;
805         }
806
807         prop = filteringprop_new(cond, action_list);
808
809 fail:
810         g_free(cond_str);
811         g_free(action_str);
812         return prop;
813 }
814
815 static void prefs_filtering_register_cb(void)
816 {
817         FilteringProp * prop;
818         
819         prop = prefs_filtering_dialog_to_filtering(TRUE);
820         if (prop == NULL)
821                 return;
822         prefs_filtering_clist_set_row(-1, prop);
823
824         filteringprop_free(prop);
825         
826         prefs_filtering_update_hscrollbar();
827 }
828
829 static void prefs_filtering_substitute_cb(void)
830 {
831         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
832         gint row;
833         FilteringProp * prop;
834         
835         if (!clist->selection) return;
836
837         row = GPOINTER_TO_INT(clist->selection->data);
838         if (row == 0) return;
839
840         prop = prefs_filtering_dialog_to_filtering(TRUE);
841         if (prop == NULL)
842                 return;
843         prefs_filtering_clist_set_row(row, prop);
844
845         filteringprop_free(prop);
846         
847         prefs_filtering_update_hscrollbar();
848 }
849
850 static void prefs_filtering_delete_cb(void)
851 {
852         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
853         gint row;
854
855         if (!clist->selection) return;
856         row = GPOINTER_TO_INT(clist->selection->data);
857         if (row == 0) 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         gtk_clist_remove(clist, row);
865
866         prefs_filtering_reset_dialog();
867
868         prefs_filtering_update_hscrollbar();
869 }
870
871 static void prefs_filtering_top(void)
872 {
873         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
874         gint row;
875
876         if (!clist->selection) return;
877
878         row = GPOINTER_TO_INT(clist->selection->data);
879         if (row > 1)
880                 gtk_clist_row_move(clist, row, 1);
881 }
882
883 static void prefs_filtering_up(void)
884 {
885         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
886         gint row;
887
888         if (!clist->selection) return;
889
890         row = GPOINTER_TO_INT(clist->selection->data);
891         if (row > 1) {
892                 gtk_clist_row_move(clist, row, row - 1);
893                 if (gtk_clist_row_is_visible(clist, row - 1) != GTK_VISIBILITY_FULL) 
894                         gtk_clist_moveto(clist, row - 1, 0, 0, 0); 
895         }
896 }
897
898 static void prefs_filtering_down(void)
899 {
900         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
901         gint row;
902
903         if (!clist->selection) return;
904
905         row = GPOINTER_TO_INT(clist->selection->data);
906         if (row > 0 && row < clist->rows - 1) {
907                 gtk_clist_row_move(clist, row, row + 1);
908                 if (gtk_clist_row_is_visible(clist, row + 1) != GTK_VISIBILITY_FULL)
909                         gtk_clist_moveto(clist, row + 1, 0, 1, 0); 
910         }
911 }
912
913 static void prefs_filtering_bottom(void)
914 {
915         GtkCList *clist = GTK_CLIST(filtering.cond_clist);
916         gint row;
917
918         if (!clist->selection) return;
919
920         row = GPOINTER_TO_INT(clist->selection->data);
921         if (row > 0 && row < clist->rows - 1)
922                 gtk_clist_row_move(clist, row, clist->rows - 1);
923 }
924
925 static void prefs_filtering_select_set(FilteringProp *prop)
926 {
927         gchar *matcher_str;
928         gchar *action_str;
929
930         prefs_filtering_reset_dialog();
931
932         matcher_str = matcherlist_to_string(prop->matchers);
933         if (matcher_str == NULL) {
934                 return;
935         }
936
937         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), matcher_str);
938
939         action_str = filteringaction_list_to_string(prop->action_list);
940         if (matcher_str == NULL) {
941                 return;
942         }
943         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), action_str);
944
945         g_free(action_str);
946         g_free(matcher_str);
947 }
948
949 static void prefs_filtering_select(GtkCList *clist, gint row, gint column,
950                                 GdkEvent *event)
951 {
952         FilteringProp * prop;
953         gchar * filtering_str;
954
955         if (row == 0) {
956                 prefs_filtering_reset_dialog();
957                 return;
958         }
959
960         if (!gtk_clist_get_text(GTK_CLIST(filtering.cond_clist),
961                                 row, 0, &filtering_str))
962                 return;
963         
964         prop = matcher_parser_get_filtering(filtering_str);
965         if (prop == NULL)
966                 return;
967
968         prefs_filtering_select_set(prop);
969
970         filteringprop_free(prop);
971 }
972
973
974 static gint prefs_filtering_deleted(GtkWidget *widget, GdkEventAny *event,
975                                  gpointer data)
976 {
977         prefs_filtering_cancel();
978         return TRUE;
979 }
980
981 static gboolean prefs_filtering_key_pressed(GtkWidget *widget, GdkEventKey *event,
982                                      gpointer data)
983 {
984         if (event && event->keyval == GDK_Escape) {
985                 prefs_filtering_cancel();
986                 return TRUE;                    
987         }
988         return FALSE;
989 }
990
991 static void prefs_filtering_ok(void)
992 {
993         FilteringProp * prop;
994         gchar * str;
995         gchar * filtering_str;
996         gint row = 1;
997         AlertValue val;
998         
999         prop = prefs_filtering_dialog_to_filtering(FALSE);
1000         if (prop != NULL) {
1001                 str = filteringprop_to_string(prop);
1002
1003                 while (gtk_clist_get_text(GTK_CLIST(filtering.cond_clist),
1004                                           row, 0, &filtering_str)) {
1005                         if (strcmp(filtering_str, str) == 0) break;
1006                         row++;
1007                 }
1008                 if (strcmp(filtering_str, str) != 0) {
1009                         val = alertpanel(_("Entry not saved"),
1010                                  _("The entry was not saved. Close anyway?"),
1011                                  _("Yes"), _("No"), NULL);
1012                         if (G_ALERTDEFAULT != val) {
1013                                 g_free(str);
1014                                 return;
1015                         }
1016                 }
1017                 g_free(str);
1018         }
1019         prefs_filtering_set_list();
1020         prefs_matcher_write_config();
1021         prefs_filtering_close();
1022 }
1023
1024 static void prefs_filtering_cancel(void)
1025 {
1026         prefs_matcher_read_config();
1027         prefs_filtering_close();
1028 }