2007-07-11 [colin] 2.10.0cvs16
[claws.git] / src / prefs_filtering.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail 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 3 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, see <http://www.gnu.org/licenses/>.
17  * 
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 "manual.h"
52 #include "combobox.h"
53
54 #include "matcher_parser.h"
55 #include "matcher.h"
56 #include "prefs_filtering_action.h"
57
58 enum {
59         PREFS_FILTERING_ENABLED,
60         PREFS_FILTERING_NAME,
61         PREFS_FILTERING_ACCOUNT_ID,
62         PREFS_FILTERING_ACCOUNT_NAME,
63         PREFS_FILTERING_RULE,
64         PREFS_FILTERING_PROP,
65         N_PREFS_FILTERING_COLUMNS
66 };
67
68 struct _Filtering {
69         GtkWidget *window;
70
71         GtkWidget *help_btn;
72         GtkWidget *ok_btn;
73         GtkWidget *name_entry;
74         GtkWidget *account_label;
75         GtkWidget *account_combobox;
76         GtkListStore *account_combobox_list;
77         GtkWidget *cond_entry;
78         GtkWidget *action_entry;
79
80         GtkWidget *cond_list_view;
81
82         GtkTreeViewColumn *account_name_column;
83 };
84
85 typedef struct _Filtering Filtering;
86
87 static Filtering  filtering;
88
89 static GSList ** p_processing_list = NULL;
90
91 /* widget creating functions */
92 static void prefs_filtering_create              (void);
93
94 static void prefs_filtering_set_dialog  (const gchar *header,
95                                          const gchar *key);
96 static void prefs_filtering_set_list    (void);
97
98 /* callback functions */
99 static void prefs_filtering_register_cb (void);
100 static void prefs_filtering_substitute_cb       (void);
101 static void prefs_filtering_delete_cb   (void);
102 static void prefs_filtering_top         (void);
103 static void prefs_filtering_up          (void);
104 static void prefs_filtering_down        (void);
105 static void prefs_filtering_bottom      (void);
106 static gint prefs_filtering_deleted     (GtkWidget      *widget,
107                                          GdkEventAny    *event,
108                                          gpointer        data);
109 static gboolean prefs_filtering_key_pressed(GtkWidget   *widget,
110                                          GdkEventKey    *event,
111                                          gpointer        data);
112 static void prefs_filtering_cancel      (void);
113 static void prefs_filtering_ok          (void);
114
115 static void prefs_filtering_condition_define    (void);
116 static void prefs_filtering_action_define(void);
117 static gint prefs_filtering_list_view_set_row   (gint row, FilteringProp * prop);
118                                           
119 static void prefs_filtering_reset_dialog        (void);
120 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data);
121 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data);
122
123 static void delete_path(GSList ** p_filters, const gchar * path);
124
125
126 static GtkListStore* prefs_filtering_create_data_store  (void);
127 static gint prefs_filtering_list_view_insert_rule       (GtkListStore *list_store,
128                                                          gint row,
129                                                          gboolean enabled,
130                                                          const gchar *name,
131                                                          gint account_id,
132                                                          const gchar *account_name,
133                                                          const gchar *rule, 
134                                                          gboolean prop);
135 static gchar *prefs_filtering_list_view_get_rule        (GtkWidget *list, 
136                                                          gint row);
137 static void prefs_filtering_list_view_get_rule_info     (GtkWidget *list, 
138                                                          gint row,
139                                                          gboolean *enabled,
140                                                          gchar **name,
141                                                          gint *account_id);
142
143 static GtkWidget *prefs_filtering_list_view_create      (void);
144 static void prefs_filtering_create_list_view_columns    (GtkWidget *list_view);
145
146 static gboolean prefs_filtering_selected                (GtkTreeSelection *selector,
147                                                          GtkTreeModel *model, 
148                                                          GtkTreePath *path,
149                                                          gboolean currently_selected,
150                                                          gpointer data);
151
152 static void prefs_filtering_account_option_menu_populate(void);
153
154 static gulong signal_id = 0; /* filtering.help_btn clicked signal */
155
156 void prefs_filtering_open(GSList ** p_processing,
157                           const gchar *title,
158                           const gchar *help_url_anchor,
159                           const gchar *header,
160                           const gchar *key,
161                           gboolean per_account_filtering)
162 {
163         if (prefs_rc_is_readonly(FILTERING_RC))
164                 return;
165
166         inc_lock();
167
168         if (!filtering.window) {
169                 prefs_filtering_create();
170         } else {
171                 gtk_list_store_clear(filtering.account_combobox_list);
172                 prefs_filtering_account_option_menu_populate();
173         }
174
175         gtk_tree_view_column_set_visible(filtering.account_name_column,
176                                                                          per_account_filtering);
177
178         manage_window_set_transient(GTK_WINDOW(filtering.window));
179         gtk_widget_grab_focus(filtering.ok_btn);
180         
181         if (title != NULL)
182                 gtk_window_set_title(GTK_WINDOW(filtering.window), title);
183         else
184                 gtk_window_set_title (GTK_WINDOW(filtering.window),
185                                       _("Filtering/Processing configuration"));
186
187         if (help_url_anchor != NULL) {
188                 if (signal_id != 0) {
189                         g_signal_handler_disconnect(
190                                         G_OBJECT(filtering.help_btn),
191                                         signal_id);
192                 }
193
194                 signal_id = g_signal_connect(G_OBJECT(filtering.help_btn),
195                                 "clicked",
196                                 G_CALLBACK(manual_open_with_anchor_cb),
197                                 (gchar*)help_url_anchor);
198         }
199         else {
200                 gtk_widget_set_sensitive(filtering.help_btn, FALSE);
201         }
202
203         p_processing_list = p_processing;
204         
205         prefs_filtering_set_dialog(header, key);
206         if (per_account_filtering) {
207                 gtk_widget_show(filtering.account_label);
208                 gtk_widget_show(filtering.account_combobox);
209         } else {
210                 gtk_widget_hide(filtering.account_label);
211                 gtk_widget_hide(filtering.account_combobox);
212                 combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
213         }
214
215         gtk_widget_show(filtering.window);
216
217         start_address_completion(NULL);
218 }
219
220 static void prefs_filtering_size_allocate_cb(GtkWidget *widget,
221                                          GtkAllocation *allocation)
222 {
223         g_return_if_fail(allocation != NULL);
224
225         prefs_common.filteringwin_width = allocation->width;
226         prefs_common.filteringwin_height = allocation->height;
227 }
228
229 /* prefs_filtering_close() - just to have one common exit point */
230 static void prefs_filtering_close(void)
231 {
232         end_address_completion();
233         
234         gtk_widget_hide(filtering.window);
235         inc_unlock();
236 }
237
238 static void prefs_filtering_account_option_menu_populate(void)
239 {
240         GList *accounts = NULL;
241         GtkTreeIter iter;
242
243         accounts = account_get_list();
244
245         g_return_if_fail(accounts != NULL);
246
247         COMBOBOX_ADD(filtering.account_combobox_list, Q_("Filtering Account Menu|All"), 0);
248         for (; accounts != NULL; accounts = accounts->next) {
249                 PrefsAccount *ac = (PrefsAccount *)accounts->data;
250
251                 COMBOBOX_ADD_ESCAPED(filtering.account_combobox_list, ac->account_name, ac->account_id);
252         }
253 }
254
255 static GtkWidget *prefs_filtering_account_option_menu(Filtering *filtering)
256 {
257         GtkWidget *optmenu = NULL;
258         GtkWidget *optmenubox = NULL;
259         GtkListStore *menu = NULL;
260         
261         optmenubox = gtk_event_box_new();
262         optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
263         menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
264
265         filtering->account_combobox = optmenu;
266         filtering->account_combobox_list = menu;
267
268         prefs_filtering_account_option_menu_populate();
269
270         return optmenubox;
271 }
272
273 static void prefs_filtering_create(void)
274 {
275         GtkWidget *window;
276         GtkWidget *vbox;
277         GtkWidget *help_btn;
278         GtkWidget *cancel_btn;
279         GtkWidget *ok_btn;
280         GtkWidget *confirm_area;
281
282         GtkWidget *vbox1;
283         GtkWidget *reg_hbox;
284         GtkWidget *arrow;
285         GtkWidget *btn_hbox;
286
287         GtkWidget *name_label;
288         GtkWidget *name_entry;
289         GtkWidget *account_label;
290         GtkWidget *account_opt_menu;
291         GtkWidget *cond_label;
292         GtkWidget *cond_entry;
293         GtkWidget *cond_btn;
294         GtkWidget *action_label;
295         GtkWidget *action_entry;
296         GtkWidget *action_btn;
297
298         GtkWidget *reg_btn;
299         GtkWidget *subst_btn;
300         GtkWidget *del_btn;
301
302         GtkWidget *cond_hbox;
303         GtkWidget *cond_scrolledwin;
304         GtkWidget *cond_list_view;
305
306         GtkWidget *btn_vbox;
307         GtkWidget *spc_vbox;
308         GtkWidget *top_btn;
309         GtkWidget *up_btn;
310         GtkWidget *down_btn;
311         GtkWidget *bottom_btn;
312         GtkWidget *table;
313         static GdkGeometry geometry;
314
315         debug_print("Creating filtering configuration window...\n");
316
317         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "prefs_filtering");
318         gtk_container_set_border_width (GTK_CONTAINER (window), 8);
319         gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
320         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
321         gtk_window_set_resizable(GTK_WINDOW (window), TRUE);
322
323         vbox = gtk_vbox_new (FALSE, 6);
324         gtk_widget_show (vbox);
325         gtk_container_add (GTK_CONTAINER (window), vbox);
326
327         gtkut_stock_button_set_create_with_help(&confirm_area, &help_btn,
328                         &cancel_btn, GTK_STOCK_CANCEL,
329                         &ok_btn, GTK_STOCK_OK,
330                         NULL, NULL);
331         gtk_widget_show (confirm_area);
332         gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
333         gtk_widget_grab_default (ok_btn);
334
335         gtk_window_set_title (GTK_WINDOW(window),
336                                     _("Filtering/Processing configuration"));
337
338         g_signal_connect(G_OBJECT(window), "delete_event",
339                          G_CALLBACK(prefs_filtering_deleted), NULL);
340         g_signal_connect(G_OBJECT(window), "size_allocate",
341                          G_CALLBACK(prefs_filtering_size_allocate_cb), NULL);
342         g_signal_connect(G_OBJECT(window), "key_press_event",
343                          G_CALLBACK(prefs_filtering_key_pressed), NULL);
344         MANAGE_WINDOW_SIGNALS_CONNECT (window);
345         g_signal_connect(G_OBJECT(ok_btn), "clicked",
346                          G_CALLBACK(prefs_filtering_ok), NULL);
347         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
348                          G_CALLBACK(prefs_filtering_cancel), NULL);
349
350         vbox1 = gtk_vbox_new (FALSE, VSPACING);
351         gtk_widget_show (vbox1);
352         gtk_box_pack_start (GTK_BOX (vbox), vbox1, FALSE, TRUE, 0);
353         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
354
355         table = gtk_table_new(4, 3, FALSE);
356         gtk_table_set_row_spacings (GTK_TABLE (table), VSPACING_NARROW_2);
357         gtk_table_set_col_spacings (GTK_TABLE (table), 4);
358         gtk_widget_show(table);
359         gtk_box_pack_start (GTK_BOX (vbox1), table, TRUE, TRUE, 0);
360
361         name_label = gtk_label_new (_("Name"));
362         gtk_widget_show (name_label);
363         gtk_misc_set_alignment (GTK_MISC (name_label), 1, 0.5);
364         gtk_table_attach (GTK_TABLE (table), name_label, 0, 1, 0, 1,
365                           (GtkAttachOptions) (GTK_FILL),
366                           (GtkAttachOptions) (0), 0, 0);
367
368         name_entry = gtk_entry_new ();
369         gtk_widget_show (name_entry);
370         gtk_table_attach (GTK_TABLE (table), name_entry, 1, 2, 0, 1,
371                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
372                           (GtkAttachOptions) (0), 0, 0);
373
374         account_label = gtk_label_new (_("Account"));
375         gtk_widget_show (account_label);
376         gtk_misc_set_alignment (GTK_MISC (account_label), 1, 0.5);
377         gtk_table_attach (GTK_TABLE (table), account_label, 0, 1, 1, 2,
378                           (GtkAttachOptions) (GTK_FILL),
379                           (GtkAttachOptions) (0), 0, 0);
380
381         account_opt_menu = prefs_filtering_account_option_menu(&filtering);
382         gtk_widget_show (account_opt_menu);
383         gtk_table_attach (GTK_TABLE (table), account_opt_menu, 1, 2, 1, 2,
384                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
385                           (GtkAttachOptions) (0), 0, 0);
386         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
387
388         cond_label = gtk_label_new (_("Condition"));
389         gtk_widget_show (cond_label);
390         gtk_misc_set_alignment (GTK_MISC (cond_label), 1, 0.5);
391         gtk_table_attach (GTK_TABLE (table), cond_label, 0, 1, 2, 3,
392                           (GtkAttachOptions) (GTK_FILL),
393                           (GtkAttachOptions) (0), 0, 0);
394
395         cond_entry = gtk_entry_new ();
396         gtk_widget_show (cond_entry);
397         gtk_table_attach (GTK_TABLE (table), cond_entry, 1, 2, 2, 3,
398                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
399                           (GtkAttachOptions) (0), 0, 0);
400
401         cond_btn = gtk_button_new_with_label (_(" Define... "));
402         gtk_widget_show (cond_btn);
403         gtk_table_attach (GTK_TABLE (table), cond_btn, 2, 3, 2, 3,
404                           (GtkAttachOptions) (GTK_FILL),
405                           (GtkAttachOptions) (0), 2, 2);
406         g_signal_connect(G_OBJECT (cond_btn), "clicked",
407                          G_CALLBACK(prefs_filtering_condition_define),
408                          NULL);
409
410         action_label = gtk_label_new (_("Action"));
411         gtk_widget_show (action_label);
412         gtk_misc_set_alignment (GTK_MISC (action_label), 1, 0.5);
413         gtk_table_attach (GTK_TABLE (table), action_label, 0, 1, 3, 4,
414                           (GtkAttachOptions) (GTK_FILL),
415                           (GtkAttachOptions) (0), 0, 0);
416
417         action_entry = gtk_entry_new ();
418         gtk_widget_show (action_entry);
419         gtk_table_attach (GTK_TABLE (table), action_entry, 1, 2, 3, 4,
420                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
421                           (GtkAttachOptions) (0), 0, 0);
422
423         action_btn = gtk_button_new_with_label (_(" Define... "));
424         gtk_widget_show (action_btn);
425         gtk_table_attach (GTK_TABLE (table), action_btn, 2, 3, 3, 4,
426                           (GtkAttachOptions) (GTK_FILL),
427                           (GtkAttachOptions) (0), 2, 2);
428         g_signal_connect(G_OBJECT (action_btn), "clicked",
429                          G_CALLBACK(prefs_filtering_action_define),
430                          NULL);
431                          
432         /* register / substitute / delete */
433         reg_hbox = gtk_hbox_new (FALSE, 4);
434         gtk_widget_show (reg_hbox);
435         gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox, FALSE, FALSE, 0);
436
437         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
438         gtk_widget_show (arrow);
439         gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0);
440         gtk_widget_set_size_request (arrow, -1, 16);
441
442         btn_hbox = gtk_hbox_new (TRUE, 4);
443         gtk_widget_show (btn_hbox);
444         gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0);
445
446         reg_btn = gtk_button_new_from_stock (GTK_STOCK_ADD);
447         gtk_widget_show (reg_btn);
448         gtk_box_pack_start (GTK_BOX (btn_hbox), reg_btn, FALSE, TRUE, 0);
449         g_signal_connect(G_OBJECT (reg_btn), "clicked",
450                          G_CALLBACK(prefs_filtering_register_cb), NULL);
451
452         subst_btn = gtkut_get_replace_btn (_("Replace"));
453         gtk_widget_show (subst_btn);
454         gtk_box_pack_start (GTK_BOX (btn_hbox), subst_btn, FALSE, TRUE, 0);
455         g_signal_connect(G_OBJECT (subst_btn), "clicked",
456                          G_CALLBACK(prefs_filtering_substitute_cb),
457                          NULL);
458
459         del_btn = gtk_button_new_from_stock (GTK_STOCK_DELETE);
460         gtk_widget_show (del_btn);
461         gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0);
462         g_signal_connect(G_OBJECT (del_btn), "clicked",
463                         G_CALLBACK(prefs_filtering_delete_cb), NULL);
464
465         cond_hbox = gtk_hbox_new (FALSE, 8);
466         gtk_widget_show (cond_hbox);
467         gtk_box_pack_start (GTK_BOX (vbox), cond_hbox, TRUE, TRUE, 0);
468
469         cond_scrolledwin = gtk_scrolled_window_new (NULL, NULL);
470         gtk_widget_show (cond_scrolledwin);
471         gtk_widget_set_size_request (cond_scrolledwin, -1, 150);
472         gtk_box_pack_start (GTK_BOX (cond_hbox), cond_scrolledwin,
473                             TRUE, TRUE, 0);
474         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cond_scrolledwin),
475                                         GTK_POLICY_AUTOMATIC,
476                                         GTK_POLICY_AUTOMATIC);
477
478         cond_list_view = prefs_filtering_list_view_create();    
479         gtk_widget_show (cond_list_view);
480         gtk_container_add (GTK_CONTAINER (cond_scrolledwin), cond_list_view);
481
482         btn_vbox = gtk_vbox_new (FALSE, 8);
483         gtk_widget_show (btn_vbox);
484         gtk_box_pack_start (GTK_BOX (cond_hbox), btn_vbox, FALSE, FALSE, 0);
485
486         top_btn = gtk_button_new_from_stock (GTK_STOCK_GOTO_TOP);
487         gtk_widget_show (top_btn);
488         gtk_box_pack_start (GTK_BOX (btn_vbox), top_btn, FALSE, FALSE, 0);
489         g_signal_connect(G_OBJECT (top_btn), "clicked",
490                          G_CALLBACK(prefs_filtering_top), NULL);
491
492         PACK_VSPACER (btn_vbox, spc_vbox, VSPACING_NARROW_2);
493
494         up_btn = gtk_button_new_from_stock (GTK_STOCK_GO_UP);
495         gtk_widget_show (up_btn);
496         gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0);
497         g_signal_connect(G_OBJECT (up_btn), "clicked",
498                          G_CALLBACK(prefs_filtering_up), NULL);
499
500         down_btn = gtk_button_new_from_stock (GTK_STOCK_GO_DOWN);
501         gtk_widget_show (down_btn);
502         gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0);
503         g_signal_connect(G_OBJECT (down_btn), "clicked",
504                          G_CALLBACK(prefs_filtering_down), NULL);
505
506         PACK_VSPACER (btn_vbox, spc_vbox, VSPACING_NARROW_2);
507
508         bottom_btn = gtk_button_new_from_stock (GTK_STOCK_GOTO_BOTTOM);
509         gtk_widget_show (bottom_btn);
510         gtk_box_pack_start (GTK_BOX (btn_vbox), bottom_btn, FALSE, FALSE, 0);
511         g_signal_connect(G_OBJECT (bottom_btn), "clicked",
512                          G_CALLBACK(prefs_filtering_bottom), NULL);
513
514         if (!geometry.min_height) {
515                 geometry.min_width = 500;
516                 geometry.min_height = 400;
517         }
518
519         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
520                                       GDK_HINT_MIN_SIZE);
521         gtk_widget_set_size_request(window, prefs_common.filteringwin_width,
522                                     prefs_common.filteringwin_height);
523
524         gtk_widget_show_all(window);
525
526         filtering.window    = window;
527         filtering.help_btn = help_btn;
528         filtering.ok_btn = ok_btn;
529
530         filtering.name_entry     = name_entry;
531         filtering.cond_entry     = cond_entry;
532         filtering.action_entry   = action_entry;
533         filtering.cond_list_view = cond_list_view;
534         filtering.account_label  = account_label;
535 }
536
537 static void rename_path(GSList * filters,
538                         const gchar * old_path, const gchar * new_path);
539
540 void prefs_filtering_rename_path(const gchar *old_path, const gchar *new_path)
541 {
542         GList * cur;
543         const gchar *paths[2] = {NULL, NULL};
544         paths[0] = old_path;
545         paths[1] = new_path;
546         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
547                 Folder *folder;
548                 folder = (Folder *) cur->data;
549                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
550                                 prefs_filtering_rename_path_func, paths);
551         }
552         
553         rename_path(pre_global_processing, old_path, new_path);
554         rename_path(post_global_processing, old_path, new_path);
555         rename_path(filtering_rules, old_path, new_path);
556         
557         prefs_matcher_write_config();
558 }
559
560 static void rename_path(GSList * filters,
561                         const gchar * old_path, const gchar * new_path)
562 {
563         gchar *base;
564         gchar *prefix;
565         gchar *suffix;
566         gchar *dest_path;
567         gchar *old_path_with_sep;
568         gint destlen;
569         gint prefixlen;
570         gint oldpathlen;
571         GSList * action_cur;
572         GSList * cur;
573
574         oldpathlen = strlen(old_path);
575         old_path_with_sep = g_strconcat(old_path,G_DIR_SEPARATOR_S,NULL);
576
577         for (cur = filters; cur != NULL; cur = cur->next) {
578                 FilteringProp   *filtering = (FilteringProp *)cur->data;
579                 
580                 for(action_cur = filtering->action_list ; action_cur != NULL ;
581                     action_cur = action_cur->next) {
582
583                         FilteringAction *action = action_cur->data;
584                         
585                         if (!action->destination) continue;
586                         
587                         destlen = strlen(action->destination);
588                         
589                         if (destlen > oldpathlen) {
590                                 prefixlen = destlen - oldpathlen;
591                                 suffix = action->destination + prefixlen;
592                                 
593                                 if (!strncmp(old_path, suffix, oldpathlen)) {
594                                         prefix = g_malloc0(prefixlen + 1);
595                                         strncpy2(prefix, action->destination, prefixlen);
596                                         
597                                         base = suffix + oldpathlen;
598                                         while (*base == G_DIR_SEPARATOR) base++;
599                                         if (*base == '\0')
600                                                 dest_path = g_strconcat(prefix,
601                                                     G_DIR_SEPARATOR_S,
602                                                     new_path, NULL);
603                                         else
604                                                 dest_path = g_strconcat(prefix,
605                                                     G_DIR_SEPARATOR_S,
606                                                     new_path,
607                                                     G_DIR_SEPARATOR_S,
608                                                     base, NULL);
609                                         
610                                         g_free(prefix);
611                                         g_free(action->destination);
612                                         action->destination = dest_path;
613                                 } else { /* for non-leaf folders */
614                                         /* compare with trailing slash */
615                                         if (!strncmp(old_path_with_sep, action->destination, oldpathlen+1)) {
616                                                 
617                                                 suffix = action->destination + oldpathlen + 1;
618                                                 dest_path = g_strconcat(new_path,
619                                                     G_DIR_SEPARATOR_S,
620                                                     suffix, NULL);
621                                                 g_free(action->destination);
622                                                 action->destination = dest_path;
623                                         }
624                                 }
625                         } else {
626                                 /* folder-moving a leaf */
627                                 if (!strcmp(old_path, action->destination)) {
628                                         dest_path = g_strdup(new_path);
629                                         g_free(action->destination);
630                                         action->destination = dest_path;
631                                 }
632                         }
633                 }
634         }
635 }
636
637 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data)
638 {
639         GSList *filters;
640         const gchar * old_path;
641         const gchar * new_path;
642         const gchar ** paths;
643         FolderItem *item;
644         
645         paths = data;
646         old_path = paths[0];
647         new_path = paths[1];
648
649         g_return_val_if_fail(old_path != NULL, FALSE);
650         g_return_val_if_fail(new_path != NULL, FALSE);
651         g_return_val_if_fail(node != NULL, FALSE);
652
653         item = node->data;
654         if (!item || !item->prefs)
655                 return FALSE;
656         filters = item->prefs->processing;
657
658         rename_path(filters, old_path, new_path);
659
660         return FALSE;
661 }
662
663 void prefs_filtering_delete_path(const gchar *path)
664 {
665         GList * cur;
666         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
667                 Folder *folder;
668                 folder = (Folder *) cur->data;
669                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
670                                 prefs_filtering_delete_path_func, (gchar *)path);
671         }
672         delete_path(&pre_global_processing, path);
673         delete_path(&post_global_processing, path);
674         delete_path(&filtering_rules, path);
675         
676         prefs_matcher_write_config();
677 }
678
679 static void delete_path(GSList ** p_filters, const gchar * path)
680 {
681         GSList * filters;
682         GSList * duplist;
683         gchar *suffix;
684         gint destlen;
685         gint prefixlen;
686         gint pathlen;
687         GSList * action_cur;
688         GSList * cur;
689         
690         filters = *p_filters;
691         pathlen = strlen(path);
692         duplist = g_slist_copy(filters);
693         for (cur = duplist ; cur != NULL; cur = g_slist_next(cur)) {
694                 FilteringProp *filtering = (FilteringProp *) cur->data;
695                 
696                 for(action_cur = filtering->action_list ; action_cur != NULL ;
697                     action_cur = action_cur->next) {
698                 
699                         FilteringAction *action;
700                         
701                         action = action_cur->data;
702                         
703                         if (!action->destination) continue;
704                         
705                         destlen = strlen(action->destination);
706                         
707                         if (destlen > pathlen) {
708                                 prefixlen = destlen - pathlen;
709                                 suffix = action->destination + prefixlen;
710                                 
711                                 if (suffix && !strncmp(path, suffix, pathlen)) {
712                                         filteringprop_free(filtering);
713                                         filters = g_slist_remove(filters, filtering);
714                                 }
715                         } else if (strcmp(action->destination, path) == 0) {
716                                 filteringprop_free(filtering);
717                                 filters = g_slist_remove(filters, filtering);
718                         }
719                 }
720         }                
721         g_slist_free(duplist);
722         
723         * p_filters = filters;
724 }
725
726 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data)
727 {
728         const gchar *path = data;
729         FolderItem *item;
730         GSList ** p_filters;
731         
732         g_return_val_if_fail(path != NULL, FALSE);
733         g_return_val_if_fail(node != NULL, FALSE);
734
735         item = node->data;
736         if (!item || !item->prefs)
737                 return FALSE;
738         p_filters = &item->prefs->processing;
739         
740         delete_path(p_filters, path);
741
742         return FALSE;
743 }
744
745 static void prefs_filtering_set_dialog(const gchar *header, const gchar *key)
746 {
747         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
748         GSList *cur;
749         GSList * prefs_filtering;
750         gchar *cond_str;
751         GtkListStore *list_store;
752         
753         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(list_view));
754         gtk_list_store_clear(list_store);
755
756         /* add the place holder (New) at row 0 */
757         prefs_filtering_list_view_insert_rule(list_store, -1, 
758                                               FALSE,
759                                               _("(New)"),
760                                                   0,
761                                               _("(New)"),
762                                               _("(New)"),
763                                               FALSE);
764
765         prefs_filtering = *p_processing_list;
766
767         for(cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur)) {
768                 FilteringProp * prop = (FilteringProp *) cur->data;
769                 gchar *account_name = NULL;
770
771                 if (prop->account_id > 0) {
772                         PrefsAccount *ac_prefs = account_find_from_id(prop->account_id);
773
774                         if (ac_prefs)
775                                 account_name = ac_prefs->account_name;
776                 }
777                 if (account_name == NULL)
778                         account_name = (gchar *)Q_("Filtering Account Menu|All");
779
780                 cond_str = filteringprop_to_string(prop);
781                 subst_char(cond_str, '\t', ':');
782
783                 prefs_filtering_list_view_insert_rule(list_store, -1, 
784                                                       prop->enabled,
785                                                       prop->name,
786                                                           prop->account_id,
787                                                           account_name,
788                                                       cond_str, TRUE);
789                 
790                 g_free(cond_str);
791         }
792
793         prefs_filtering_reset_dialog();
794
795         if (header && key) {
796                 gchar * quoted_key;
797                 gchar *match_str;
798
799                 quoted_key = matcher_quote_str(key);
800                 
801                 match_str = g_strconcat(header, " ", get_matchparser_tab_str(MATCHTYPE_MATCHCASE),
802                                         " \"", quoted_key, "\"", NULL);
803                 g_free(quoted_key);
804                 
805                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), match_str);
806                 g_free(match_str);
807         }
808 }
809
810 static void prefs_filtering_reset_dialog(void)
811 {
812         gtk_entry_set_text(GTK_ENTRY(filtering.name_entry), "");
813         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
814         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), "");
815         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), "");
816 }
817
818 static void prefs_filtering_set_list(void)
819 {
820         gint row = 1;
821         FilteringProp *prop;
822         GSList * cur;
823         gchar * filtering_str;
824         GSList * prefs_filtering;
825
826         prefs_filtering = *p_processing_list;
827         for (cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur))
828                 filteringprop_free((FilteringProp *) cur->data);
829         g_slist_free(prefs_filtering);
830         prefs_filtering = NULL;
831         
832
833         while (NULL != (filtering_str = prefs_filtering_list_view_get_rule
834                                                 (filtering.cond_list_view, row))) {
835                 /* FIXME: this strcmp() is bogus: "(New)" should never
836                  * be inserted in the storage */
837                 if (strcmp(filtering_str, _("(New)")) != 0) {
838                         gboolean enabled;
839                         gchar *name;
840                         gint account_id = 0;
841
842                         prefs_filtering_list_view_get_rule_info(
843                                         filtering.cond_list_view, row,
844                                         &enabled, &name, &account_id);
845                         prop = matcher_parser_get_filtering(filtering_str);
846                         g_free(filtering_str);
847                         if (prop) {
848                                 prop->enabled = enabled;
849                                 prop->name = name;
850                                 prop->account_id = account_id;
851                                 prefs_filtering = 
852                                         g_slist_append(prefs_filtering, prop);
853                         }
854                 }
855                 
856                 row++;
857         }                               
858         
859         *p_processing_list = prefs_filtering;
860 }
861
862 static gint prefs_filtering_list_view_set_row(gint row, FilteringProp * prop)
863 {
864         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
865         gchar *str;
866         GtkListStore *list_store;
867         gchar *name = NULL;
868         gint account_id = 0;
869         gchar *account_name = (gchar *)Q_("Filtering Account Menu|All");
870         gboolean enabled = TRUE;
871
872         str = filteringprop_to_string(prop);
873         if (str == NULL)
874                 return -1;
875
876         if (prop) {
877                 if (prop->name)
878                         name = prop->name;
879                 account_id = prop->account_id;
880                 if (account_id > 0)
881                         account_name = account_find_from_id(account_id)->account_name;
882                 enabled = prop->enabled;
883         }
884
885         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(list_view));
886
887         row = prefs_filtering_list_view_insert_rule(list_store, row,
888                                                     enabled,
889                                                     name,
890                                                     account_id,
891                                                         account_name,
892                                                     str,
893                                                     prop != NULL);
894
895         g_free(str);
896
897         return row;
898 }
899
900 static void prefs_filtering_condition_define_done(MatcherList * matchers)
901 {
902         gchar * str;
903
904         if (matchers == NULL)
905                 return;
906
907         str = matcherlist_to_string(matchers);
908
909         if (str != NULL) {
910                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), str);
911                 g_free(str);
912         }
913 }
914
915 static void prefs_filtering_condition_define(void)
916 {
917         gchar * cond_str;
918         MatcherList * matchers = NULL;
919
920         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
921
922         if (*cond_str != '\0') {
923                 matchers = matcher_parser_get_cond(cond_str, NULL);
924                 if (matchers == NULL)
925                         alertpanel_error(_("Condition string is not valid."));
926         }
927         
928         g_free(cond_str);
929
930         prefs_matcher_open(matchers, prefs_filtering_condition_define_done);
931
932         if (matchers != NULL)
933                 matcherlist_free(matchers);
934 }
935
936 static void prefs_filtering_action_define_done(GSList * action_list)
937 {
938         gchar * str;
939
940         if (action_list == NULL)
941                 return;
942
943         str = filteringaction_list_to_string(action_list);
944
945         if (str != NULL) {
946                 gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), str);
947                 g_free(str);
948         }
949 }
950
951 static void prefs_filtering_action_define(void)
952 {
953         gchar * action_str;
954         GSList * action_list = NULL;
955
956         action_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
957
958         if (*action_str != '\0') {
959                 action_list = matcher_parser_get_action_list(action_str);
960                 if (action_list == NULL)
961                         alertpanel_error(_("Action string is not valid."));
962         }
963         
964         g_free(action_str);
965
966         prefs_filtering_action_open(action_list,
967             prefs_filtering_action_define_done);
968
969         if (action_list != NULL) {
970                 GSList * cur;
971                 for(cur = action_list ; cur != NULL ; cur = cur->next) {
972                         filteringaction_free(cur->data);
973                 }
974         }
975 }
976
977
978 /* register / substitute delete buttons */
979
980
981 static FilteringProp * prefs_filtering_dialog_to_filtering(gboolean alert)
982 {
983         MatcherList * cond;
984         gboolean enabled = TRUE;
985         gchar * name = NULL;
986         gint account_id = 0;
987         gchar * cond_str = NULL;
988         gchar * action_str = NULL;
989         FilteringProp * prop = NULL;
990         GSList * action_list;
991
992         name = gtk_editable_get_chars(GTK_EDITABLE(filtering.name_entry), 0, -1);
993         
994         account_id = combobox_get_active_data(GTK_COMBO_BOX(filtering.account_combobox));
995
996         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
997         if (*cond_str == '\0') {
998                 if(alert == TRUE) alertpanel_error(_("Condition string is empty."));
999                 goto fail;
1000         }
1001         
1002         action_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
1003         if (*action_str == '\0') {
1004                 if(alert == TRUE) alertpanel_error(_("Action string is empty."));
1005                 goto fail;
1006         }
1007
1008         cond = matcher_parser_get_cond(cond_str, NULL);
1009
1010         if (cond == NULL) {
1011                 if(alert == TRUE) alertpanel_error(_("Condition string is not valid."));
1012                 goto fail;
1013         }
1014         
1015         action_list = matcher_parser_get_action_list(action_str);
1016         
1017
1018         if (action_list == NULL) {
1019                 if(alert == TRUE) alertpanel_error(_("Action string is not valid."));
1020                 goto fail;
1021         }
1022
1023         prop = filteringprop_new(enabled, name, account_id, cond, action_list);
1024
1025 fail:
1026         g_free(name);
1027         g_free(cond_str);
1028         g_free(action_str);
1029         return prop;
1030 }
1031
1032 static void prefs_filtering_register_cb(void)
1033 {
1034         FilteringProp * prop;
1035         
1036         prop = prefs_filtering_dialog_to_filtering(TRUE);
1037         if (prop == NULL)
1038                 return;
1039         prefs_filtering_list_view_set_row(-1, prop);
1040         
1041         filteringprop_free(prop);
1042
1043         prefs_filtering_reset_dialog();
1044 }
1045
1046 static void prefs_filtering_substitute_cb(void)
1047 {
1048         gint selected_row = gtkut_list_view_get_selected_row
1049                 (filtering.cond_list_view);
1050         FilteringProp *prop;
1051         gboolean enabled;
1052         gchar *name;
1053         gint account_id;
1054         
1055         if (selected_row <= 0)
1056                 return;
1057
1058         prop = prefs_filtering_dialog_to_filtering(TRUE);
1059
1060         if (prop == NULL) 
1061                 return;
1062
1063         /* prop->emabled is always TRUE here, re-use the value from the selected row 
1064            as we don't substitute this value from dialog */
1065         prefs_filtering_list_view_get_rule_info(
1066                         filtering.cond_list_view, selected_row,
1067                         &enabled, &name, &account_id);
1068         prop->enabled = enabled;
1069
1070         prefs_filtering_list_view_set_row(selected_row, prop);
1071
1072         filteringprop_free(prop);
1073
1074         prefs_filtering_reset_dialog();
1075 }
1076
1077 static void prefs_filtering_delete_cb(void)
1078 {
1079         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
1080         GtkTreeModel *model;
1081         GtkTreeIter iter;
1082         gint row;
1083         
1084         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1085         if (row <= 0) 
1086                 return; 
1087
1088         if (alertpanel(_("Delete rule"),
1089                        _("Do you really want to delete this rule?"),
1090                        GTK_STOCK_CANCEL, "+"GTK_STOCK_DELETE, NULL) == G_ALERTDEFAULT)
1091                 return;
1092
1093         model = gtk_tree_view_get_model(list_view);     
1094         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1095                 return;
1096
1097         gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1098
1099         prefs_filtering_reset_dialog();
1100 }
1101
1102 static void prefs_filtering_top(void)
1103 {
1104         gint row;
1105         GtkTreeIter top, sel;
1106         GtkTreeModel *model;
1107
1108         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1109         if (row <= 1) 
1110                 return;
1111
1112         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));               
1113         
1114         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, 0)
1115         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
1116                 return;
1117
1118         gtk_list_store_move_after(GTK_LIST_STORE(model), &sel, &top);
1119         gtkut_list_view_select_row(filtering.cond_list_view, 1);
1120 }
1121
1122 static void prefs_filtering_up(void)
1123 {
1124         gint row;
1125         GtkTreeIter top, sel;
1126         GtkTreeModel *model;
1127
1128         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1129         if (row <= 1) 
1130                 return;
1131                 
1132         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1133
1134         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row - 1)
1135         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
1136                 return;
1137
1138         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
1139         gtkut_list_view_select_row(filtering.cond_list_view, row - 1);
1140 }
1141
1142 static void prefs_filtering_down(void)
1143 {
1144         gint row, n_rows;
1145         GtkTreeIter top, sel;
1146         GtkTreeModel *model;
1147
1148         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1149         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1150         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1151         if (row < 1 || row >= n_rows - 1)
1152                 return;
1153
1154         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
1155         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row + 1))
1156                 return;
1157                         
1158         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
1159         gtkut_list_view_select_row(filtering.cond_list_view, row + 1);
1160 }
1161
1162 static void prefs_filtering_bottom(void)
1163 {
1164         gint row, n_rows;
1165         GtkTreeIter top, sel;
1166         GtkTreeModel *model;
1167
1168         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1169         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1170         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1171         if (row < 1 || row >= n_rows - 1)
1172                 return;
1173
1174         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
1175         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, n_rows - 1))
1176                 return;
1177
1178         gtk_list_store_move_after(GTK_LIST_STORE(model), &top, &sel);           
1179         gtkut_list_view_select_row(filtering.cond_list_view, n_rows - 1);
1180 }
1181
1182 static void prefs_filtering_select_set(FilteringProp *prop)
1183 {
1184         gchar *matcher_str;
1185         gchar *action_str;
1186
1187         prefs_filtering_reset_dialog();
1188
1189         matcher_str = matcherlist_to_string(prop->matchers);
1190         if (matcher_str == NULL) {
1191                 return;
1192         }
1193         
1194         if (prop->name != NULL)
1195                 gtk_entry_set_text(GTK_ENTRY(filtering.name_entry), prop->name);
1196
1197         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), prop->account_id);
1198                 
1199         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), matcher_str);
1200
1201         action_str = filteringaction_list_to_string(prop->action_list);
1202         if (matcher_str == NULL) {
1203                 return;
1204         }
1205         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), action_str);
1206
1207         g_free(action_str);
1208         g_free(matcher_str);
1209 }
1210
1211 static gint prefs_filtering_deleted(GtkWidget *widget, GdkEventAny *event,
1212                                  gpointer data)
1213 {
1214         prefs_filtering_cancel();
1215         return TRUE;
1216 }
1217
1218 static gboolean prefs_filtering_key_pressed(GtkWidget *widget, GdkEventKey *event,
1219                                      gpointer data)
1220 {
1221         if (event && event->keyval == GDK_Escape) {
1222                 prefs_filtering_cancel();
1223                 return TRUE;                    
1224         }
1225         return FALSE;
1226 }
1227
1228 static void prefs_filtering_ok(void)
1229 {
1230         FilteringProp * prop;
1231         gchar * str;
1232         gchar * filtering_str;
1233         gint row = 1;
1234         AlertValue val;
1235         
1236         prop = prefs_filtering_dialog_to_filtering(FALSE);
1237         if (prop != NULL) {
1238                 str = filteringprop_to_string(prop);
1239
1240                 while (NULL != (filtering_str = (prefs_filtering_list_view_get_rule
1241                                                         (filtering.cond_list_view,
1242                                                          row)))) {
1243                         if (strcmp(filtering_str, str) == 0)
1244                                 break;
1245                         row++;
1246                         g_free(filtering_str);
1247                 }       
1248
1249                 if (!filtering_str) {
1250                         val = alertpanel(_("Entry not saved"),
1251                                  _("The entry was not saved. Close anyway?"),
1252                                  GTK_STOCK_CLOSE, _("+_Continue editing"), NULL);
1253                         if (G_ALERTDEFAULT != val) {
1254                                 g_free(filtering_str);
1255                                 g_free(str); /* fixed two leaks: huzzah! */
1256                                 filteringprop_free(prop);
1257                                 return;
1258                         }
1259                 }               
1260
1261                 g_free(filtering_str);
1262                 g_free(str);
1263                 filteringprop_free(prop); /* fixed a leak: huzzah! */
1264         } else {
1265                 gchar *name, *condition, *action;
1266                 name = gtk_editable_get_chars(GTK_EDITABLE(filtering.name_entry), 0, -1);
1267                 condition = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
1268                 action = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
1269                 if (strlen(name) || 
1270                     strlen(condition) || 
1271                     strlen(action)) {
1272                         val = alertpanel(_("Entry not saved"),
1273                                  _("The entry was not saved. Close anyway?"),
1274                                  GTK_STOCK_CLOSE, _("+_Continue editing"), NULL);
1275                         if (G_ALERTDEFAULT != val) {
1276                                 g_free(name);
1277                                 g_free(condition);
1278                                 g_free(action);
1279                                 return;
1280                         }
1281                 }
1282                 g_free(name);
1283                 g_free(condition);
1284                 g_free(action);
1285         }
1286         prefs_filtering_set_list();
1287         prefs_matcher_write_config();
1288         prefs_filtering_close();
1289 }
1290
1291 static void prefs_filtering_cancel(void)
1292 {
1293         prefs_matcher_read_config();
1294         prefs_filtering_close();
1295 }
1296
1297 static GtkListStore* prefs_filtering_create_data_store(void)
1298 {
1299         return gtk_list_store_new(N_PREFS_FILTERING_COLUMNS,
1300                                   G_TYPE_BOOLEAN,
1301                                   G_TYPE_STRING,
1302                                   G_TYPE_INT,
1303                                   G_TYPE_STRING,
1304                                   G_TYPE_STRING,
1305                                   G_TYPE_BOOLEAN,
1306                                  -1);
1307 }
1308
1309 /*!
1310  *\brief        Insert filtering rule into store. Note that we access the
1311  *              tree view / store by index, which is a bit suboptimal, but
1312  *              at least it made GTK 2 porting easier.
1313  *
1314  *\param        list_store Store to operate on
1315  *\param        row -1 to add a new rule to store, else change an existing
1316  *              row
1317  *\param        enabled TRUE if rule is enabled
1318  *\param        name The Name of rule
1319  *\param        account_id The account ID
1320  *\param        account_name The account name or All or (New)
1321  *\param        rule String representation of rule
1322  *\param        prop TRUE if valid filtering rule; if FALSE it's the first
1323  *              entry in the store ("(New)").
1324  *
1325  *\return       int Row of inserted / changed rule.
1326  */
1327 static gint prefs_filtering_list_view_insert_rule(GtkListStore *list_store,
1328                                                   gint row,
1329                                                   gboolean enabled,
1330                                                   const gchar *name,
1331                                                   gint account_id,
1332                                                   const gchar *account_name,
1333                                                   const gchar *rule,
1334                                                   gboolean prop) 
1335 {
1336         GtkTreeIter iter;
1337
1338         /* check if valid row at all */
1339         if (row >= 0) {
1340                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
1341                                                    &iter, NULL, row))
1342                         row = -1;                                                  
1343         }
1344
1345         if (row < 0) {
1346                 /* append new */
1347                 gtk_list_store_append(list_store, &iter);
1348                 gtk_list_store_set(list_store, &iter, 
1349                                    PREFS_FILTERING_ENABLED, enabled,
1350                                    PREFS_FILTERING_NAME, name,
1351                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1352                                    PREFS_FILTERING_ACCOUNT_NAME, account_name,
1353                                    PREFS_FILTERING_RULE, rule,
1354                                    PREFS_FILTERING_PROP, prop,
1355                                    -1);
1356                 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store),
1357                                                       NULL) - 1;
1358         } else {
1359                 /* change existing */
1360                 gtk_list_store_set(list_store, &iter, 
1361                                    PREFS_FILTERING_ENABLED, enabled,
1362                                    PREFS_FILTERING_NAME, name,
1363                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1364                                    PREFS_FILTERING_ACCOUNT_NAME, account_name,
1365                                    PREFS_FILTERING_RULE, rule,
1366                                    -1);
1367                 return row;                                
1368         }
1369 }
1370
1371 /*!
1372  *\return       gchar * Rule at specified row - should be freed.
1373  */
1374 static gchar *prefs_filtering_list_view_get_rule(GtkWidget *list, gint row)
1375 {       
1376         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1377         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1378         GtkTreeIter iter;
1379         gchar *result;
1380
1381         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1382                 return NULL;
1383         
1384         gtk_tree_model_get(model, &iter, 
1385                            PREFS_FILTERING_RULE, &result,
1386                            -1);
1387         
1388         return result;
1389 }
1390
1391 static void prefs_filtering_list_view_get_rule_info(GtkWidget *list, gint row,
1392                                 gboolean *enabled, gchar **name, gint *account_id)
1393 {       
1394         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1395         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1396         GtkTreeIter iter;
1397
1398         *enabled = TRUE;
1399         *name = NULL;
1400
1401         if (gtk_tree_model_iter_nth_child(model, &iter, NULL, row)) {
1402                 gtk_tree_model_get(model, &iter, 
1403                                    PREFS_FILTERING_ENABLED, enabled,
1404                                    PREFS_FILTERING_NAME, name,
1405                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1406                                    -1);
1407         }
1408 }
1409
1410 /*!
1411  *\brief        Create list view for filtering
1412  */
1413 static GtkWidget *prefs_filtering_list_view_create(void)
1414 {
1415         GtkTreeView *list_view;
1416         GtkTreeSelection *selector;
1417
1418         list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL
1419                 (prefs_filtering_create_data_store())));
1420         
1421         gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
1422         gtk_tree_view_set_reorderable(list_view, TRUE);
1423         
1424         selector = gtk_tree_view_get_selection(list_view);
1425         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
1426         gtk_tree_selection_set_select_function(selector, prefs_filtering_selected,
1427                                                NULL, NULL);
1428
1429         /* create the columns */
1430         prefs_filtering_create_list_view_columns(GTK_WIDGET(list_view));
1431
1432         return GTK_WIDGET(list_view);
1433 }
1434
1435 static void prefs_filtering_enable_toggled(GtkCellRendererToggle *widget,
1436                 gchar *path,
1437                 GtkWidget *list_view)
1438 {
1439         GtkTreeIter iter;
1440         GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list_view));
1441         gboolean enabled = TRUE;
1442
1443         if (!gtk_tree_model_get_iter_from_string(model, &iter, path))
1444                 return;
1445
1446         gtk_tree_model_get(model, &iter,
1447                            PREFS_FILTERING_ENABLED, &enabled,
1448                            -1);
1449
1450         gtk_list_store_set(GTK_LIST_STORE(model), &iter,
1451                            PREFS_FILTERING_ENABLED, !enabled,
1452                            -1);
1453 }
1454
1455 static void prefs_filtering_create_list_view_columns(GtkWidget *list_view)
1456 {
1457         GtkTreeViewColumn *column;
1458         GtkCellRenderer *renderer;
1459
1460         renderer = gtk_cell_renderer_toggle_new();
1461         g_object_set(renderer,
1462                      "radio", FALSE,
1463                      "activatable", TRUE,
1464                      NULL);
1465         column = gtk_tree_view_column_new_with_attributes
1466                 (_("Enable"), /* FIXME : Enable, Enabled, or 'E' ? */
1467                  renderer,
1468                  "active", PREFS_FILTERING_ENABLED,
1469                  NULL);
1470         gtk_tree_view_column_set_alignment (column, 0.5);
1471         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1472         g_signal_connect(G_OBJECT(renderer), "toggled",
1473                          G_CALLBACK(prefs_filtering_enable_toggled),
1474                          list_view);
1475
1476         renderer = gtk_cell_renderer_text_new();
1477         column = gtk_tree_view_column_new_with_attributes
1478                 (_("Name"),
1479                  renderer,
1480                  "text", PREFS_FILTERING_NAME,
1481                  NULL);
1482         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1483         gtk_tree_view_column_set_resizable(column, TRUE);
1484
1485         renderer = gtk_cell_renderer_text_new();
1486         column = gtk_tree_view_column_new_with_attributes
1487                 (_("Account"),
1488                  renderer,
1489                  "text", PREFS_FILTERING_ACCOUNT_NAME,
1490                  NULL);
1491         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1492         gtk_tree_view_column_set_resizable(column, TRUE);
1493
1494         filtering.account_name_column = column;
1495                 
1496         renderer = gtk_cell_renderer_text_new();
1497         column = gtk_tree_view_column_new_with_attributes
1498                 (_("Rule"),
1499                  renderer,
1500                  "text", PREFS_FILTERING_RULE,
1501                  NULL);
1502         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
1503 }
1504
1505 /*!
1506  *\brief        Triggered when a row is selected
1507  */
1508 static gboolean prefs_filtering_selected(GtkTreeSelection *selector,
1509                                          GtkTreeModel *model, 
1510                                          GtkTreePath *path,
1511                                          gboolean currently_selected,
1512                                          gpointer data)
1513 {
1514         if (currently_selected)
1515                 return TRUE;
1516         else {          
1517                 gboolean has_prop  = FALSE;
1518                 GtkTreeIter iter;
1519
1520                 gtk_tree_model_get_iter(model, &iter, path);
1521                 gtk_tree_model_get(model, &iter,
1522                                    PREFS_FILTERING_PROP, &has_prop,
1523                                    -1);
1524
1525                 if (has_prop) {
1526                         FilteringProp *prop;
1527                         gchar *filtering_str = NULL;
1528                         gchar *name = NULL;
1529                         gint account_id = 0;
1530
1531                         gtk_tree_model_get(model, &iter,
1532                                            PREFS_FILTERING_RULE, &filtering_str,
1533                                            -1);
1534                         gtk_tree_model_get(model, &iter,
1535                                            PREFS_FILTERING_NAME, &name,
1536                                            -1);
1537                         gtk_tree_model_get(model, &iter,
1538                                            PREFS_FILTERING_ACCOUNT_ID, &account_id,
1539                                            -1);
1540
1541                         prop = matcher_parser_get_filtering(filtering_str);
1542                         if (prop) {
1543                                 prop->name = g_strdup(name);
1544                                 prop->account_id = account_id;
1545                                 prefs_filtering_select_set(prop);
1546                                 filteringprop_free(prop);
1547                         }                               
1548                         g_free(name);
1549                         g_free(filtering_str);
1550                 } else
1551                         prefs_filtering_reset_dialog();
1552         }               
1553
1554         return TRUE;
1555 }
1556