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