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