2011-04-10 [colin] 3.7.9cvs9
[claws.git] / src / prefs_filtering.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2011 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 <gdk/gdkkeysyms.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34
35 #include "main.h"
36 #include "prefs_gtk.h"
37 #include "prefs_matcher.h"
38 #include "prefs_filtering.h"
39 #include "prefs_common.h"
40 #include "mainwindow.h"
41 #include "foldersel.h"
42 #include "manage_window.h"
43 #include "inc.h"
44 #include "utils.h"
45 #include "gtkutils.h"
46 #include "alertpanel.h"
47 #include "folder.h"
48 #include "filtering.h"
49 #include "addr_compl.h"
50 #include "manual.h"
51 #include "combobox.h"
52 #include "menu.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 gboolean prefs_filtering_search_func_cb (GtkTreeModel *model, gint column, 
100                                                 const gchar *key, GtkTreeIter *iter, 
101                                                 gpointer search_data);
102 static void prefs_filtering_register_cb (gpointer action, gpointer data);
103 static void prefs_filtering_substitute_cb       (gpointer action, gpointer data);
104 static void prefs_filtering_delete_cb   (gpointer action, gpointer data);
105 static void prefs_filtering_delete_all_cb(gpointer action, gpointer data);
106 static void prefs_filtering_clear_cb(gpointer action, gpointer data);
107 static void prefs_filtering_duplicate_cb(gpointer action, gpointer data);
108 static void prefs_filtering_top         (gpointer action, gpointer data);
109 static void prefs_filtering_page_up     (gpointer action, gpointer data);
110 static void prefs_filtering_up          (gpointer action, gpointer data);
111 static void prefs_filtering_down        (gpointer action, gpointer data);
112 static void prefs_filtering_page_down   (gpointer action, gpointer data);
113 static void prefs_filtering_bottom      (gpointer action, gpointer data);
114 static gint prefs_filtering_deleted     (GtkWidget      *widget,
115                                          GdkEventAny    *event,
116                                          gpointer        data);
117 static gboolean prefs_filtering_key_pressed(GtkWidget   *widget,
118                                          GdkEventKey    *event,
119                                          gpointer        data);
120 static void prefs_filtering_cancel      (gpointer action, gpointer data);
121 static void prefs_filtering_ok          (gpointer action, gpointer data);
122
123 static void prefs_filtering_condition_define    (gpointer action, gpointer data);
124 static void prefs_filtering_action_define       (gpointer action, gpointer data);
125 static gint prefs_filtering_list_view_set_row   (gint row, FilteringProp * prop);
126                                           
127 static void prefs_filtering_reset_dialog        (void);
128 static gboolean prefs_filtering_rename_tag_func(GNode *node, gpointer data);
129 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data);
130 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data);
131
132 static void delete_path(GSList ** p_filters, const gchar * path);
133
134
135 static GtkListStore* prefs_filtering_create_data_store  (void);
136 static gint prefs_filtering_list_view_insert_rule       (GtkListStore *list_store,
137                                                          gint row,
138                                                          gboolean enabled,
139                                                          const gchar *name,
140                                                          gint account_id,
141                                                          const gchar *account_name,
142                                                          const gchar *rule, 
143                                                          gboolean prop);
144 static gchar *prefs_filtering_list_view_get_rule        (GtkWidget *list, 
145                                                          gint row);
146 static void prefs_filtering_list_view_get_rule_info     (GtkWidget *list, 
147                                                          gint row,
148                                                          gboolean *enabled,
149                                                          gchar **name,
150                                                          gint *account_id);
151
152 static GtkWidget *prefs_filtering_list_view_create      (void);
153 static void prefs_filtering_create_list_view_columns    (GtkWidget *list_view);
154
155 static void prefs_filtering_select_row(GtkTreeView *list_view, GtkTreePath *path);
156
157 static void prefs_filtering_account_option_menu_populate(void);
158
159 static gulong signal_id = 0; /* filtering.help_btn clicked signal */
160
161 static int modified = FALSE;
162
163 void prefs_filtering_open(GSList ** p_processing,
164                           const gchar *title,
165                           const gchar *help_url_anchor,
166                           const gchar *header,
167                           const gchar *key,
168                           gboolean per_account_filtering)
169 {
170         if (prefs_rc_is_readonly(FILTERING_RC))
171                 return;
172
173         inc_lock();
174
175         if (!filtering.window) {
176                 prefs_filtering_create();
177         } else {
178                 gtk_list_store_clear(filtering.account_combobox_list);
179                 prefs_filtering_account_option_menu_populate();
180         }
181
182         gtk_tree_view_column_set_visible(filtering.account_name_column,
183                                                                          per_account_filtering);
184
185         manage_window_set_transient(GTK_WINDOW(filtering.window));
186         gtk_widget_grab_focus(filtering.ok_btn);
187         
188         if (title != NULL)
189                 gtk_window_set_title(GTK_WINDOW(filtering.window), title);
190         else
191                 gtk_window_set_title (GTK_WINDOW(filtering.window),
192                                       _("Filtering/Processing configuration"));
193
194         if (help_url_anchor != NULL) {
195                 if (signal_id != 0) {
196                         g_signal_handler_disconnect(
197                                         G_OBJECT(filtering.help_btn),
198                                         signal_id);
199                 }
200
201                 signal_id = g_signal_connect(G_OBJECT(filtering.help_btn),
202                                 "clicked",
203                                 G_CALLBACK(manual_open_with_anchor_cb),
204                                 (gchar*)help_url_anchor);
205         }
206         else {
207                 gtk_widget_set_sensitive(filtering.help_btn, FALSE);
208         }
209
210         p_processing_list = p_processing;
211         
212         prefs_filtering_set_dialog(header, key);
213         if (per_account_filtering) {
214                 gtk_widget_show(filtering.account_label);
215                 gtk_widget_show(filtering.account_combobox);
216         } else {
217                 gtk_widget_hide(filtering.account_label);
218                 gtk_widget_hide(filtering.account_combobox);
219                 combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
220         }
221
222         gtk_widget_show(filtering.window);
223         gtk_window_set_modal(GTK_WINDOW(filtering.window), TRUE);
224
225         start_address_completion(NULL);
226 }
227
228 static void prefs_filtering_size_allocate_cb(GtkWidget *widget,
229                                          GtkAllocation *allocation)
230 {
231         cm_return_if_fail(allocation != NULL);
232
233         prefs_common.filteringwin_width = allocation->width;
234         prefs_common.filteringwin_height = allocation->height;
235 }
236
237 /* prefs_filtering_close() - just to have one common exit point */
238 static void prefs_filtering_close(void)
239 {
240         GtkListStore *store;
241
242         end_address_completion();
243
244         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW
245                                 (filtering.cond_list_view)));
246         gtk_list_store_clear(store);
247         gtk_widget_hide(filtering.window);
248         gtk_window_set_modal(GTK_WINDOW(filtering.window), FALSE);
249         inc_unlock();
250 }
251
252 static void prefs_filtering_account_option_menu_populate(void)
253 {
254         GList *accounts = NULL;
255         GtkTreeIter iter;
256
257         accounts = account_get_list();
258
259         cm_return_if_fail(accounts != NULL);
260
261         COMBOBOX_ADD(filtering.account_combobox_list, Q_("Filtering Account Menu|All"), 0);
262         COMBOBOX_ADD(filtering.account_combobox_list, NULL, 0);
263         for (; accounts != NULL; accounts = accounts->next) {
264                 PrefsAccount *ac = (PrefsAccount *)accounts->data;
265
266                 COMBOBOX_ADD_ESCAPED(filtering.account_combobox_list, ac->account_name, ac->account_id);
267         }
268 }
269
270 static GtkWidget *prefs_filtering_account_option_menu(Filtering *filtering)
271 {
272         GtkWidget *optmenu = NULL;
273         GtkWidget *optmenubox = NULL;
274         GtkListStore *menu = NULL;
275         
276         optmenubox = gtk_event_box_new();
277         optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
278         menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
279
280         filtering->account_combobox = optmenu;
281         filtering->account_combobox_list = menu;
282
283         prefs_filtering_account_option_menu_populate();
284
285         return optmenubox;
286 }
287
288 static void prefs_filtering_create(void)
289 {
290         GtkWidget *window;
291         GtkWidget *vbox;
292         GtkWidget *help_btn;
293         GtkWidget *cancel_btn;
294         GtkWidget *ok_btn;
295         GtkWidget *confirm_area;
296
297         GtkWidget *vbox1;
298         GtkWidget *reg_hbox;
299         GtkWidget *arrow;
300         GtkWidget *btn_hbox;
301
302         GtkWidget *name_label;
303         GtkWidget *name_entry;
304         GtkWidget *account_label;
305         GtkWidget *account_opt_menu;
306         GtkWidget *cond_label;
307         GtkWidget *cond_entry;
308         GtkWidget *cond_btn;
309         GtkWidget *action_label;
310         GtkWidget *action_entry;
311         GtkWidget *action_btn;
312
313         GtkWidget *reg_btn;
314         GtkWidget *subst_btn;
315         GtkWidget *del_btn;
316         GtkWidget *clear_btn;
317
318         GtkWidget *cond_hbox;
319         GtkWidget *cond_scrolledwin;
320         GtkWidget *cond_list_view;
321
322         GtkWidget *btn_vbox;
323         GtkWidget *top_btn;
324         GtkWidget *up_btn;
325         GtkWidget *down_btn;
326 #ifndef GENERIC_UMPC
327         GtkWidget *page_up_btn;
328         GtkWidget *page_down_btn;
329 #endif
330         GtkWidget *bottom_btn;
331         GtkWidget *table;
332         static GdkGeometry geometry;
333         CLAWS_TIP_DECL();
334
335         debug_print("Creating filtering configuration window...\n");
336
337         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "prefs_filtering");
338         gtk_container_set_border_width (GTK_CONTAINER (window), 8);
339         gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
340         gtk_window_set_resizable(GTK_WINDOW (window), TRUE);
341
342         vbox = gtk_vbox_new (FALSE, 6);
343         gtk_widget_show (vbox);
344         gtk_container_add (GTK_CONTAINER (window), vbox);
345
346         gtkut_stock_button_set_create_with_help(&confirm_area, &help_btn,
347                         &cancel_btn, GTK_STOCK_CANCEL,
348                         &ok_btn, GTK_STOCK_OK,
349                         NULL, NULL);
350         gtk_widget_show (confirm_area);
351         gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
352         gtk_widget_grab_default (ok_btn);
353
354         gtk_window_set_title (GTK_WINDOW(window),
355                                     _("Filtering/Processing configuration"));
356
357         g_signal_connect(G_OBJECT(window), "delete_event",
358                          G_CALLBACK(prefs_filtering_deleted), NULL);
359         g_signal_connect(G_OBJECT(window), "size_allocate",
360                          G_CALLBACK(prefs_filtering_size_allocate_cb), NULL);
361         g_signal_connect(G_OBJECT(window), "key_press_event",
362                          G_CALLBACK(prefs_filtering_key_pressed), NULL);
363         MANAGE_WINDOW_SIGNALS_CONNECT (window);
364         g_signal_connect(G_OBJECT(ok_btn), "clicked",
365                          G_CALLBACK(prefs_filtering_ok), NULL);
366         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
367                          G_CALLBACK(prefs_filtering_cancel), NULL);
368
369         vbox1 = gtk_vbox_new (FALSE, VSPACING);
370         gtk_widget_show (vbox1);
371         gtk_box_pack_start (GTK_BOX (vbox), vbox1, FALSE, TRUE, 0);
372         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
373
374         table = gtk_table_new(4, 3, FALSE);
375         gtk_table_set_row_spacings (GTK_TABLE (table), VSPACING_NARROW_2);
376         gtk_table_set_col_spacings (GTK_TABLE (table), 4);
377         gtk_widget_show(table);
378         gtk_box_pack_start (GTK_BOX (vbox1), table, TRUE, TRUE, 0);
379
380         name_label = gtk_label_new (_("Name"));
381         gtk_widget_show (name_label);
382         gtk_misc_set_alignment (GTK_MISC (name_label), 1, 0.5);
383         gtk_table_attach (GTK_TABLE (table), name_label, 0, 1, 0, 1,
384                           (GtkAttachOptions) (GTK_FILL),
385                           (GtkAttachOptions) (0), 0, 0);
386
387         name_entry = gtk_entry_new ();
388         gtk_widget_show (name_entry);
389         gtk_table_attach (GTK_TABLE (table), name_entry, 1, 2, 0, 1,
390                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
391                           (GtkAttachOptions) (0), 0, 0);
392
393         account_label = gtk_label_new (_("Account"));
394         gtk_widget_show (account_label);
395         gtk_misc_set_alignment (GTK_MISC (account_label), 1, 0.5);
396         gtk_table_attach (GTK_TABLE (table), account_label, 0, 1, 1, 2,
397                           (GtkAttachOptions) (GTK_FILL),
398                           (GtkAttachOptions) (0), 0, 0);
399
400         account_opt_menu = prefs_filtering_account_option_menu(&filtering);
401         gtk_widget_show (account_opt_menu);
402         gtk_table_attach (GTK_TABLE (table), account_opt_menu, 1, 2, 1, 2,
403                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
404                           (GtkAttachOptions) (0), 0, 0);
405         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
406
407         cond_label = gtk_label_new (_("Condition"));
408         gtk_widget_show (cond_label);
409         gtk_misc_set_alignment (GTK_MISC (cond_label), 1, 0.5);
410         gtk_table_attach (GTK_TABLE (table), cond_label, 0, 1, 2, 3,
411                           (GtkAttachOptions) (GTK_FILL),
412                           (GtkAttachOptions) (0), 0, 0);
413
414         cond_entry = gtk_entry_new ();
415         gtk_widget_show (cond_entry);
416         gtk_table_attach (GTK_TABLE (table), cond_entry, 1, 2, 2, 3,
417                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
418                           (GtkAttachOptions) (0), 0, 0);
419
420         cond_btn = gtk_button_new_with_label (_(" Define... "));
421         gtk_widget_show (cond_btn);
422         gtk_table_attach (GTK_TABLE (table), cond_btn, 2, 3, 2, 3,
423                           (GtkAttachOptions) (GTK_FILL),
424                           (GtkAttachOptions) (0), 2, 2);
425         g_signal_connect(G_OBJECT (cond_btn), "clicked",
426                          G_CALLBACK(prefs_filtering_condition_define),
427                          NULL);
428
429         action_label = gtk_label_new (_("Action"));
430         gtk_widget_show (action_label);
431         gtk_misc_set_alignment (GTK_MISC (action_label), 1, 0.5);
432         gtk_table_attach (GTK_TABLE (table), action_label, 0, 1, 3, 4,
433                           (GtkAttachOptions) (GTK_FILL),
434                           (GtkAttachOptions) (0), 0, 0);
435
436         action_entry = gtk_entry_new ();
437         gtk_widget_show (action_entry);
438         gtk_table_attach (GTK_TABLE (table), action_entry, 1, 2, 3, 4,
439                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
440                           (GtkAttachOptions) (0), 0, 0);
441
442         action_btn = gtk_button_new_with_label (_(" Define... "));
443         gtk_widget_show (action_btn);
444         gtk_table_attach (GTK_TABLE (table), action_btn, 2, 3, 3, 4,
445                           (GtkAttachOptions) (GTK_FILL),
446                           (GtkAttachOptions) (0), 2, 2);
447         g_signal_connect(G_OBJECT (action_btn), "clicked",
448                          G_CALLBACK(prefs_filtering_action_define),
449                          NULL);
450                          
451         /* register / substitute / delete */
452         reg_hbox = gtk_hbox_new (FALSE, 4);
453         gtk_widget_show (reg_hbox);
454         gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox, FALSE, FALSE, 0);
455
456         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
457         gtk_widget_show (arrow);
458         gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0);
459         gtk_widget_set_size_request (arrow, -1, 16);
460
461         btn_hbox = gtk_hbox_new (TRUE, 4);
462         gtk_widget_show (btn_hbox);
463         gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0);
464
465         reg_btn = gtk_button_new_from_stock (GTK_STOCK_ADD);
466         gtk_widget_show (reg_btn);
467         gtk_box_pack_start (GTK_BOX (btn_hbox), reg_btn, FALSE, TRUE, 0);
468         g_signal_connect(G_OBJECT (reg_btn), "clicked",
469                          G_CALLBACK(prefs_filtering_register_cb), NULL);
470         CLAWS_SET_TIP(reg_btn,
471                         _("Append the new rule above to the list"));
472
473         subst_btn = gtkut_get_replace_btn (_("Replace"));
474         gtk_widget_show (subst_btn);
475         gtk_box_pack_start (GTK_BOX (btn_hbox), subst_btn, FALSE, TRUE, 0);
476         g_signal_connect(G_OBJECT (subst_btn), "clicked",
477                          G_CALLBACK(prefs_filtering_substitute_cb),
478                          NULL);
479         CLAWS_SET_TIP(subst_btn,
480                         _("Replace the selected rule in list with the rule above"));
481
482         del_btn = gtk_button_new_from_stock (GTK_STOCK_DELETE);
483         gtk_widget_show (del_btn);
484         gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0);
485         g_signal_connect(G_OBJECT (del_btn), "clicked",
486                         G_CALLBACK(prefs_filtering_delete_cb), NULL);
487         CLAWS_SET_TIP(del_btn,
488                         _("Delete the selected rule from the list"));
489
490         clear_btn = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
491         gtk_widget_show (clear_btn);
492         gtk_box_pack_start (GTK_BOX (btn_hbox), clear_btn, FALSE, TRUE, 0);
493         g_signal_connect(G_OBJECT (clear_btn), "clicked",
494                         G_CALLBACK(prefs_filtering_clear_cb), NULL);
495         CLAWS_SET_TIP(clear_btn,
496                         _("Clear all the input fields in the dialog"));
497
498         cond_hbox = gtk_hbox_new (FALSE, 8);
499         gtk_widget_show (cond_hbox);
500         gtk_box_pack_start (GTK_BOX (vbox), cond_hbox, TRUE, TRUE, 0);
501
502         cond_scrolledwin = gtk_scrolled_window_new (NULL, NULL);
503         gtk_widget_show (cond_scrolledwin);
504         gtk_widget_set_size_request (cond_scrolledwin, -1, 150);
505         gtk_box_pack_start (GTK_BOX (cond_hbox), cond_scrolledwin,
506                             TRUE, TRUE, 0);
507         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cond_scrolledwin),
508                                         GTK_POLICY_AUTOMATIC,
509                                         GTK_POLICY_AUTOMATIC);
510
511         cond_list_view = prefs_filtering_list_view_create();    
512         gtk_widget_show (cond_list_view);
513         gtk_container_add (GTK_CONTAINER (cond_scrolledwin), cond_list_view);
514
515         btn_vbox = gtk_vbox_new (FALSE, 8);
516         gtk_widget_show (btn_vbox);
517         gtk_box_pack_start (GTK_BOX (cond_hbox), btn_vbox, FALSE, FALSE, 0);
518
519         top_btn = gtk_button_new_from_stock (GTK_STOCK_GOTO_TOP);
520         gtk_widget_show (top_btn);
521         gtk_box_pack_start (GTK_BOX (btn_vbox), top_btn, FALSE, FALSE, 0);
522         g_signal_connect(G_OBJECT (top_btn), "clicked",
523                          G_CALLBACK(prefs_filtering_top), NULL);
524         CLAWS_SET_TIP(top_btn,
525                         _("Move the selected rule to the top"));
526
527 #ifndef GENERIC_UMPC
528         page_up_btn = gtk_button_new_with_mnemonic (_("Page up"));
529         gtk_button_set_image(GTK_BUTTON(page_up_btn),
530                         gtk_image_new_from_stock(GTK_STOCK_GO_UP,GTK_ICON_SIZE_BUTTON));
531         gtk_widget_show (page_up_btn);
532         gtk_box_pack_start (GTK_BOX (btn_vbox), page_up_btn, FALSE, FALSE, 0);
533         g_signal_connect(G_OBJECT (page_up_btn), "clicked",
534                          G_CALLBACK(prefs_filtering_page_up), NULL);
535         CLAWS_SET_TIP(page_up_btn,
536                         _("Move the selected rule one page up"));
537 #endif
538
539         up_btn = gtk_button_new_from_stock (GTK_STOCK_GO_UP);
540         gtk_widget_show (up_btn);
541         gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0);
542         g_signal_connect(G_OBJECT (up_btn), "clicked",
543                          G_CALLBACK(prefs_filtering_up), NULL);
544         CLAWS_SET_TIP(up_btn,
545                         _("Move the selected rule up"));
546
547         down_btn = gtk_button_new_from_stock (GTK_STOCK_GO_DOWN);
548         gtk_widget_show (down_btn);
549         gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0);
550         g_signal_connect(G_OBJECT (down_btn), "clicked",
551                          G_CALLBACK(prefs_filtering_down), NULL);
552         CLAWS_SET_TIP(down_btn,
553                         _("Move the selected rule down"));
554
555 #ifndef GENERIC_UMPC
556         page_down_btn = gtk_button_new_with_mnemonic (_("Page down"));
557         gtk_button_set_image(GTK_BUTTON(page_down_btn),
558                         gtk_image_new_from_stock(GTK_STOCK_GO_DOWN,GTK_ICON_SIZE_BUTTON));
559         gtk_widget_show (page_down_btn);
560         gtk_box_pack_start (GTK_BOX (btn_vbox), page_down_btn, FALSE, FALSE, 0);
561         g_signal_connect(G_OBJECT (page_down_btn), "clicked",
562                          G_CALLBACK(prefs_filtering_page_down), NULL);
563         CLAWS_SET_TIP(page_down_btn,
564                         _("Move the selected rule one page down"));
565 #endif
566
567         bottom_btn = gtk_button_new_from_stock (GTK_STOCK_GOTO_BOTTOM);
568         gtk_widget_show (bottom_btn);
569         gtk_box_pack_start (GTK_BOX (btn_vbox), bottom_btn, FALSE, FALSE, 0);
570         g_signal_connect(G_OBJECT (bottom_btn), "clicked",
571                          G_CALLBACK(prefs_filtering_bottom), NULL);
572         CLAWS_SET_TIP(bottom_btn,
573                         _("Move the selected rule to the bottom"));
574
575         if (!geometry.min_height) {
576                 geometry.min_width = 500;
577                 geometry.min_height = 460;
578         }
579
580         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
581                                       GDK_HINT_MIN_SIZE);
582         gtk_widget_set_size_request(window, prefs_common.filteringwin_width,
583                                     prefs_common.filteringwin_height);
584
585         gtk_widget_show_all(window);
586
587         filtering.window    = window;
588         filtering.help_btn = help_btn;
589         filtering.ok_btn = ok_btn;
590
591         filtering.name_entry     = name_entry;
592         filtering.cond_entry     = cond_entry;
593         filtering.action_entry   = action_entry;
594         filtering.cond_list_view = cond_list_view;
595         filtering.account_label  = account_label;
596 }
597
598 static void rename_tag(GSList * filters,
599                         const gchar * old_tag, const gchar * new_tag);
600
601 void prefs_filtering_rename_tag(const gchar *old_tag, const gchar *new_tag)
602 {
603         GList * cur;
604         const gchar *tags[2] = {NULL, NULL};
605         tags[0] = old_tag;
606         tags[1] = new_tag;
607         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
608                 Folder *folder;
609                 folder = (Folder *) cur->data;
610                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
611                                 prefs_filtering_rename_tag_func, tags);
612         }
613         
614         rename_tag(pre_global_processing, old_tag, new_tag);
615         rename_tag(post_global_processing, old_tag, new_tag);
616         rename_tag(filtering_rules, old_tag, new_tag);
617         
618         prefs_matcher_write_config();
619 }
620
621
622 static void rename_path(GSList * filters,
623                         const gchar * old_path, const gchar * new_path);
624
625 void prefs_filtering_rename_path(const gchar *old_path, const gchar *new_path)
626 {
627         GList * cur;
628         const gchar *paths[2] = {NULL, NULL};
629         paths[0] = old_path;
630         paths[1] = new_path;
631         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
632                 Folder *folder;
633                 folder = (Folder *) cur->data;
634                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
635                                 prefs_filtering_rename_path_func, paths);
636         }
637         
638         rename_path(pre_global_processing, old_path, new_path);
639         rename_path(post_global_processing, old_path, new_path);
640         rename_path(filtering_rules, old_path, new_path);
641         
642         prefs_matcher_write_config();
643 }
644
645 static void rename_path(GSList * filters,
646                         const gchar * old_path, const gchar * new_path)
647 {
648         gchar *base;
649         gchar *prefix;
650         gchar *suffix;
651         gchar *dest_path;
652         gchar *old_path_with_sep;
653         gint destlen;
654         gint prefixlen;
655         gint oldpathlen;
656         GSList * action_cur;
657         GSList * cur;
658         const gchar *separator=G_DIR_SEPARATOR_S;
659         gboolean matched = FALSE;
660 #ifdef G_OS_WIN32
661 again:
662 #endif
663         oldpathlen = strlen(old_path);
664         old_path_with_sep = g_strconcat(old_path,separator,NULL);
665
666         for (cur = filters; cur != NULL; cur = cur->next) {
667                 FilteringProp   *filtering = (FilteringProp *)cur->data;
668                 
669                 for(action_cur = filtering->action_list ; action_cur != NULL ;
670                     action_cur = action_cur->next) {
671
672                         FilteringAction *action = action_cur->data;
673                         
674                         if (action->type == MATCHACTION_SET_TAG ||
675                             action->type == MATCHACTION_UNSET_TAG)
676                                 continue;
677                         if (!action->destination) 
678                                 continue;
679                         
680                         destlen = strlen(action->destination);
681                         
682                         if (destlen > oldpathlen) {
683                                 prefixlen = destlen - oldpathlen;
684                                 suffix = action->destination + prefixlen;
685                                 
686                                 if (!strncmp(old_path, suffix, oldpathlen)) {
687                                         prefix = g_malloc0(prefixlen + 1);
688                                         strncpy2(prefix, action->destination, prefixlen);
689                                         
690                                         base = suffix + oldpathlen;
691                                         while (*base == G_DIR_SEPARATOR) base++;
692                                         if (*base == '\0')
693                                                 dest_path = g_strconcat(prefix,
694                                                     separator,
695                                                     new_path, NULL);
696                                         else
697                                                 dest_path = g_strconcat(prefix,
698                                                     separator,
699                                                     new_path,
700                                                     separator,
701                                                     base, NULL);
702                                         
703                                         g_free(prefix);
704                                         g_free(action->destination);
705                                         action->destination = dest_path;
706                                         matched = TRUE;
707                                 } else { /* for non-leaf folders */
708                                         /* compare with trailing slash */
709                                         if (!strncmp(old_path_with_sep, action->destination, oldpathlen+1)) {
710                                                 
711                                                 suffix = action->destination + oldpathlen + 1;
712                                                 dest_path = g_strconcat(new_path,
713                                                     separator,
714                                                     suffix, NULL);
715                                                 g_free(action->destination);
716                                                 action->destination = dest_path;
717                                                 matched = TRUE;
718                                         }
719                                 }
720                         } else {
721                                 /* folder-moving a leaf */
722                                 if (!strcmp(old_path, action->destination)) {
723                                         dest_path = g_strdup(new_path);
724                                         g_free(action->destination);
725                                         action->destination = dest_path;
726                                         matched = TRUE;
727                                 }
728                         }
729                 }
730         }
731         
732         g_free(old_path_with_sep);
733 #ifdef G_OS_WIN32
734         if (!strcmp(separator, G_DIR_SEPARATOR_S) && !matched) {
735                 separator = "/";
736                 goto again;
737         }
738 #endif
739 }
740
741 static gboolean prefs_filtering_rename_path_func(GNode *node, gpointer data)
742 {
743         GSList *filters;
744         const gchar * old_path;
745         const gchar * new_path;
746         const gchar ** paths;
747         FolderItem *item;
748         
749         paths = data;
750         old_path = paths[0];
751         new_path = paths[1];
752
753         cm_return_val_if_fail(old_path != NULL, FALSE);
754         cm_return_val_if_fail(new_path != NULL, FALSE);
755         cm_return_val_if_fail(node != NULL, FALSE);
756
757         item = node->data;
758         if (!item || !item->prefs)
759                 return FALSE;
760         filters = item->prefs->processing;
761
762         rename_path(filters, old_path, new_path);
763
764         return FALSE;
765 }
766
767 static void rename_tag(GSList * filters,
768                         const gchar * old_tag, const gchar * new_tag)
769 {
770         GSList * action_cur;
771         GSList * cur;
772
773         for (cur = filters; cur != NULL; cur = cur->next) {
774                 FilteringProp   *filtering = (FilteringProp *)cur->data;
775                 
776                 for(action_cur = filtering->action_list ; action_cur != NULL ;
777                     action_cur = action_cur->next) {
778
779                         FilteringAction *action = action_cur->data;
780                         
781                         if (action->type != MATCHACTION_SET_TAG &&
782                             action->type != MATCHACTION_UNSET_TAG)
783                                 continue;
784                         if (!action->destination)
785                                 continue;
786                         if (!strcmp(action->destination, old_tag)) {
787                                 g_free(action->destination);
788                                 action->destination = g_strdup(new_tag);
789                         }
790                 }
791         }
792 }
793
794 static gboolean prefs_filtering_rename_tag_func(GNode *node, gpointer data)
795 {
796         GSList *filters;
797         const gchar * old_tag;
798         const gchar * new_tag;
799         const gchar ** tags;
800         FolderItem *item;
801         
802         tags = data;
803         old_tag = tags[0];
804         new_tag = tags[1];
805
806         cm_return_val_if_fail(old_tag != NULL, FALSE);
807         cm_return_val_if_fail(new_tag != NULL, FALSE);
808         cm_return_val_if_fail(node != NULL, FALSE);
809
810         item = node->data;
811         if (!item || !item->prefs)
812                 return FALSE;
813         filters = item->prefs->processing;
814
815         rename_tag(filters, old_tag, new_tag);
816
817         return FALSE;
818 }
819
820 void prefs_filtering_delete_path(const gchar *path)
821 {
822         GList * cur;
823         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
824                 Folder *folder;
825                 folder = (Folder *) cur->data;
826                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
827                                 prefs_filtering_delete_path_func, (gchar *)path);
828         }
829         delete_path(&pre_global_processing, path);
830         delete_path(&post_global_processing, path);
831         delete_path(&filtering_rules, path);
832         
833         prefs_matcher_write_config();
834 }
835
836 static void delete_path(GSList ** p_filters, const gchar * path)
837 {
838         GSList * filters;
839         GSList * duplist;
840         gchar *suffix;
841         gint destlen;
842         gint prefixlen;
843         gint pathlen;
844         GSList * action_cur;
845         GSList * cur;
846         
847         filters = *p_filters;
848         pathlen = strlen(path);
849         duplist = g_slist_copy(filters);
850         for (cur = duplist ; cur != NULL; cur = g_slist_next(cur)) {
851                 FilteringProp *filtering = (FilteringProp *) cur->data;
852                 
853                 for(action_cur = filtering->action_list ; action_cur != NULL ;
854                     action_cur = action_cur->next) {
855                 
856                         FilteringAction *action;
857                         
858                         action = action_cur->data;
859                         
860                         if (action->type == MATCHACTION_SET_TAG ||
861                             action->type == MATCHACTION_UNSET_TAG)
862                                 continue;
863                         if (!action->destination) 
864                                 continue;
865                         
866                         destlen = strlen(action->destination);
867                         
868                         if (destlen > pathlen) {
869                                 prefixlen = destlen - pathlen;
870                                 suffix = action->destination + prefixlen;
871                                 
872                                 if (suffix && !strncmp(path, suffix, pathlen)) {
873                                         filteringprop_free(filtering);
874                                         filters = g_slist_remove(filters, filtering);
875                                 }
876                         } else if (strcmp(action->destination, path) == 0) {
877                                 filteringprop_free(filtering);
878                                 filters = g_slist_remove(filters, filtering);
879                         }
880                 }
881         }                
882         g_slist_free(duplist);
883         
884         * p_filters = filters;
885 }
886
887 static gboolean prefs_filtering_delete_path_func(GNode *node, gpointer data)
888 {
889         const gchar *path = data;
890         FolderItem *item;
891         GSList ** p_filters;
892         
893         cm_return_val_if_fail(path != NULL, FALSE);
894         cm_return_val_if_fail(node != NULL, FALSE);
895
896         item = node->data;
897         if (!item || !item->prefs)
898                 return FALSE;
899         p_filters = &item->prefs->processing;
900         
901         delete_path(p_filters, path);
902
903         return FALSE;
904 }
905
906 static void prefs_filtering_clear_list(GtkListStore *list_store)
907 {
908         gtk_list_store_clear(list_store);
909
910         /* add the place holder (New) at row 0 */
911         prefs_filtering_list_view_insert_rule(list_store, -1, 
912                                               FALSE,
913                                               _("(New)"),
914                                                   0,
915                                               _("(New)"),
916                                               _("(New)"),
917                                               FALSE);
918 }
919
920 static void prefs_filtering_set_dialog(const gchar *header, const gchar *key)
921 {
922         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
923         GSList *cur;
924         GSList * prefs_filtering;
925         gchar *cond_str;
926         GtkListStore *list_store;
927         
928         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(list_view));
929         prefs_filtering_clear_list(list_store);
930
931         prefs_filtering = *p_processing_list;
932
933         for(cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur)) {
934                 FilteringProp * prop = (FilteringProp *) cur->data;
935                 gchar *account_name = NULL;
936
937                 if (prop->account_id > 0) {
938                         PrefsAccount *ac_prefs = account_find_from_id(prop->account_id);
939
940                         if (ac_prefs)
941                                 account_name = ac_prefs->account_name;
942                 }
943                 if (account_name == NULL)
944                         account_name = (gchar *)Q_("Filtering Account Menu|All");
945
946                 cond_str = filteringprop_to_string(prop);
947                 subst_char(cond_str, '\t', ':');
948
949                 prefs_filtering_list_view_insert_rule(list_store, -1, 
950                                                       prop->enabled,
951                                                       prop->name,
952                                                           prop->account_id,
953                                                           account_name,
954                                                       cond_str, TRUE);
955                 
956                 g_free(cond_str);
957         }
958
959         prefs_filtering_reset_dialog();
960
961         if (header && key) {
962                 gchar * quoted_key;
963                 gchar *match_str;
964
965                 quoted_key = matcher_quote_str(key);
966                 
967                 match_str = g_strconcat(header, " ", get_matchparser_tab_str(MATCHTYPE_MATCHCASE),
968                                         " \"", quoted_key, "\"", NULL);
969                 g_free(quoted_key);
970                 
971                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), match_str);
972                 g_free(match_str);
973         }
974 }
975
976 static void prefs_filtering_reset_dialog(void)
977 {
978         gtk_entry_set_text(GTK_ENTRY(filtering.name_entry), "");
979         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), 0);
980         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), "");
981         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), "");
982 }
983
984 static gboolean prefs_filtering_search_func_cb (GtkTreeModel *model, gint column, const gchar *key, 
985                                                 GtkTreeIter *iter, gpointer search_data) 
986 {
987         gchar *store_string;
988         gboolean retval;
989         GtkTreePath *path;
990
991         gtk_tree_model_get (model, iter, column, &store_string, -1);
992
993         if (!store_string || !key) return FALSE;
994
995
996         retval = (strncmp (key, store_string, strlen(key)) != 0);
997
998         g_free(store_string);
999         debug_print("selecting row\n");
1000         path = gtk_tree_model_get_path(model, iter);
1001         prefs_filtering_select_row(GTK_TREE_VIEW(filtering.cond_list_view), path);
1002         gtk_tree_path_free(path);
1003
1004         return retval;
1005 }
1006
1007 static void prefs_filtering_set_list(void)
1008 {
1009         gint row = 1;
1010         FilteringProp *prop;
1011         GSList * cur;
1012         gchar * filtering_str;
1013         GSList * prefs_filtering;
1014
1015         prefs_filtering = *p_processing_list;
1016         for (cur = prefs_filtering ; cur != NULL ; cur = g_slist_next(cur))
1017                 filteringprop_free((FilteringProp *) cur->data);
1018         g_slist_free(prefs_filtering);
1019         prefs_filtering = NULL;
1020         
1021
1022         while (NULL != (filtering_str = prefs_filtering_list_view_get_rule
1023                                                 (filtering.cond_list_view, row))) {
1024                 /* FIXME: this strcmp() is bogus: "(New)" should never
1025                  * be inserted in the storage */
1026                 if (strcmp(filtering_str, _("(New)")) != 0) {
1027                         gboolean enabled;
1028                         gchar *name;
1029                         gint account_id = 0;
1030
1031                         prefs_filtering_list_view_get_rule_info(
1032                                         filtering.cond_list_view, row,
1033                                         &enabled, &name, &account_id);
1034                         prop = matcher_parser_get_filtering(filtering_str);
1035                         g_free(filtering_str);
1036                         if (prop) {
1037                                 prop->enabled = enabled;
1038                                 prop->name = name;
1039                                 prop->account_id = account_id;
1040                                 prefs_filtering = 
1041                                         g_slist_append(prefs_filtering, prop);
1042                         }
1043                 }
1044                 
1045                 row++;
1046         }                               
1047         
1048         *p_processing_list = prefs_filtering;
1049 }
1050
1051 static gint prefs_filtering_list_view_set_row(gint row, FilteringProp * prop)
1052 {
1053         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
1054         gchar *str = NULL;
1055         GtkListStore *list_store;
1056         gchar *name = NULL;
1057         gint account_id = 0;
1058         gchar *account_name = (gchar *)Q_("Filtering Account Menu|All");
1059         gboolean enabled = TRUE;
1060
1061         if (prop)
1062                 str = filteringprop_to_string(prop);
1063         if (str == NULL)
1064                 return -1;
1065
1066         if (prop) {
1067                 if (prop->name)
1068                         name = prop->name;
1069                 account_id = prop->account_id;
1070                 if (account_id > 0)
1071                         account_name = account_find_from_id(account_id)->account_name;
1072                 enabled = prop->enabled;
1073         }
1074
1075         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(list_view));
1076
1077         row = prefs_filtering_list_view_insert_rule(list_store, row,
1078                                                     enabled,
1079                                                     name,
1080                                                     account_id,
1081                                                         account_name,
1082                                                     str,
1083                                                     prop != NULL);
1084
1085         g_free(str);
1086
1087         return row;
1088 }
1089
1090 static void prefs_filtering_condition_define_done(MatcherList * matchers)
1091 {
1092         gchar * str;
1093
1094         if (matchers == NULL)
1095                 return;
1096
1097         str = matcherlist_to_string(matchers);
1098
1099         if (str != NULL) {
1100                 gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), str);
1101                 g_free(str);
1102         }
1103 }
1104
1105 static void prefs_filtering_condition_define(gpointer action, gpointer data)
1106 {
1107         gchar * cond_str;
1108         MatcherList * matchers = NULL;
1109
1110         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
1111
1112         if (*cond_str != '\0') {
1113                 matchers = matcher_parser_get_cond(cond_str, NULL);
1114                 if (matchers == NULL)
1115                         alertpanel_error(_("Condition string is not valid."));
1116         }
1117         
1118         g_free(cond_str);
1119
1120         prefs_matcher_open(matchers, prefs_filtering_condition_define_done);
1121
1122         if (matchers != NULL)
1123                 matcherlist_free(matchers);
1124 }
1125
1126 static void prefs_filtering_action_define_done(GSList * action_list)
1127 {
1128         gchar * str;
1129
1130         if (action_list == NULL)
1131                 return;
1132
1133         str = filteringaction_list_to_string(action_list);
1134
1135         if (str != NULL) {
1136                 gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), str);
1137                 g_free(str);
1138         }
1139 }
1140
1141 static void prefs_filtering_action_define(gpointer action, gpointer data)
1142 {
1143         gchar * action_str;
1144         GSList * action_list = NULL;
1145
1146         action_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
1147
1148         if (*action_str != '\0') {
1149                 action_list = matcher_parser_get_action_list(action_str);
1150                 if (action_list == NULL)
1151                         alertpanel_error(_("Action string is not valid."));
1152         }
1153         
1154         g_free(action_str);
1155
1156         prefs_filtering_action_open(action_list,
1157             prefs_filtering_action_define_done);
1158
1159         if (action_list != NULL) {
1160                 GSList * cur;
1161                 for(cur = action_list ; cur != NULL ; cur = cur->next) {
1162                         filteringaction_free(cur->data);
1163                 }
1164         }
1165 }
1166
1167
1168 /* register / substitute delete buttons */
1169
1170
1171 static FilteringProp * prefs_filtering_dialog_to_filtering(gboolean alert)
1172 {
1173         MatcherList * cond;
1174         gboolean enabled = TRUE;
1175         gchar * name = NULL;
1176         gint account_id = 0;
1177         gchar * cond_str = NULL;
1178         gchar * action_str = NULL;
1179         FilteringProp * prop = NULL;
1180         GSList * action_list;
1181
1182         name = gtk_editable_get_chars(GTK_EDITABLE(filtering.name_entry), 0, -1);
1183         
1184         account_id = combobox_get_active_data(GTK_COMBO_BOX(filtering.account_combobox));
1185
1186         cond_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
1187         if (*cond_str == '\0') {
1188                 if(alert == TRUE) alertpanel_error(_("Condition string is empty."));
1189                 goto fail;
1190         }
1191         
1192         action_str = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
1193         if (*action_str == '\0') {
1194                 if(alert == TRUE) alertpanel_error(_("Action string is empty."));
1195                 goto fail;
1196         }
1197
1198         cond = matcher_parser_get_cond(cond_str, NULL);
1199
1200         if (cond == NULL) {
1201                 if(alert == TRUE) alertpanel_error(_("Condition string is not valid."));
1202                 goto fail;
1203         }
1204         
1205         action_list = matcher_parser_get_action_list(action_str);
1206         
1207
1208         if (action_list == NULL) {
1209                 if(alert == TRUE) alertpanel_error(_("Action string is not valid."));
1210                 goto fail;
1211         }
1212
1213         prop = filteringprop_new(enabled, name, account_id, cond, action_list);
1214
1215 fail:
1216         g_free(name);
1217         g_free(cond_str);
1218         g_free(action_str);
1219         return prop;
1220 }
1221
1222 static void prefs_filtering_register_cb(gpointer action, gpointer data)
1223 {
1224         FilteringProp *prop;
1225         
1226         prop = prefs_filtering_dialog_to_filtering(TRUE);
1227         if (prop == NULL)
1228                 return;
1229         prefs_filtering_list_view_set_row(-1, prop);
1230         
1231         filteringprop_free(prop);
1232
1233         prefs_filtering_reset_dialog();
1234         modified = TRUE;
1235 }
1236
1237 static void prefs_filtering_substitute_cb(gpointer action, gpointer data)
1238 {
1239         gint selected_row = gtkut_list_view_get_selected_row
1240                 (filtering.cond_list_view);
1241         FilteringProp *prop;
1242         gboolean enabled;
1243         gchar *name;
1244         gint account_id;
1245         
1246         if (selected_row <= 0)
1247                 return;
1248
1249         prop = prefs_filtering_dialog_to_filtering(TRUE);
1250
1251         if (prop == NULL) 
1252                 return;
1253
1254         /* prop->emabled is always TRUE here, re-use the value from the selected row 
1255            as we don't substitute this value from dialog */
1256         prefs_filtering_list_view_get_rule_info(
1257                         filtering.cond_list_view, selected_row,
1258                         &enabled, &name, &account_id);
1259         prop->enabled = enabled;
1260
1261         prefs_filtering_list_view_set_row(selected_row, prop);
1262
1263         filteringprop_free(prop);
1264
1265         prefs_filtering_reset_dialog();
1266         modified = TRUE;
1267 }
1268
1269 static void prefs_filtering_delete_cb(gpointer action, gpointer data)
1270 {
1271         GtkTreeView *list_view = GTK_TREE_VIEW(filtering.cond_list_view);
1272         GtkTreeModel *model;
1273         GtkTreeIter iter;
1274         gint selected_row;
1275         
1276         selected_row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1277         if (selected_row <= 0) 
1278                 return; 
1279
1280         if (alertpanel(_("Delete rule"),
1281                        _("Do you really want to delete this rule?"),
1282                        GTK_STOCK_CANCEL, "+"GTK_STOCK_DELETE, NULL) == G_ALERTDEFAULT)
1283                 return;
1284
1285         model = gtk_tree_view_get_model(list_view);     
1286         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, selected_row))
1287                 return;
1288
1289         gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1290
1291         prefs_filtering_reset_dialog();
1292         modified = TRUE;
1293 }
1294
1295 static void prefs_filtering_delete_all_cb(gpointer action, gpointer data)
1296 {
1297         GtkListStore *list_store;
1298         
1299         if (alertpanel(_("Delete all rules"),
1300                        _("Do you really want to delete all the rules?"),
1301                        GTK_STOCK_CANCEL, "+"GTK_STOCK_DELETE, NULL) == G_ALERTDEFAULT)
1302                 return;
1303
1304         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view)));
1305         prefs_filtering_clear_list(list_store);
1306
1307         prefs_filtering_reset_dialog();
1308         modified = TRUE;
1309 }
1310
1311 static void prefs_filtering_clear_cb(gpointer action, gpointer data)
1312 {
1313         prefs_filtering_reset_dialog();
1314 }
1315
1316 static void prefs_filtering_duplicate_cb(gpointer action, gpointer data)
1317 {
1318         gint selected_row = gtkut_list_view_get_selected_row
1319                 (filtering.cond_list_view);     
1320         FilteringProp *prop;
1321         gboolean enabled;
1322         gchar *name;
1323         gint account_id;
1324
1325         if (selected_row <= 0) 
1326                 return; 
1327         
1328         prop = prefs_filtering_dialog_to_filtering(TRUE);
1329         if (prop == NULL)
1330                 return;
1331
1332         /* prop->emabled is always TRUE here, re-use the value from the selected row 
1333            as we don't substitute this value from dialog */
1334         prefs_filtering_list_view_get_rule_info(
1335                         filtering.cond_list_view, selected_row,
1336                         &enabled, &name, &account_id);
1337         prop->enabled = enabled;
1338
1339         prefs_filtering_list_view_set_row(-selected_row-2, prop);
1340         
1341         filteringprop_free(prop);
1342
1343         prefs_filtering_reset_dialog();
1344         modified = TRUE;
1345 }
1346
1347 static void prefs_filtering_top(gpointer action, gpointer data)
1348 {
1349         gint row;
1350         GtkTreeIter top, sel;
1351         GtkTreeModel *model;
1352
1353         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1354         if (row <= 1) 
1355                 return;
1356
1357         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));               
1358         
1359         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, 0)
1360         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
1361                 return;
1362
1363         gtk_list_store_move_after(GTK_LIST_STORE(model), &sel, &top);
1364         gtkut_list_view_select_row(filtering.cond_list_view, 1);
1365         modified = TRUE;
1366 }
1367
1368 static void prefs_filtering_page_up(gpointer action, gpointer data)
1369 {
1370         gint row, target_row, n_rows;
1371         GtkTreeIter selected, target;
1372         GtkTreeModel *model;
1373         GtkTreePath *path;
1374         GdkRectangle cell_rect, view_rect;
1375
1376         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1377         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1378         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1379         if (row <= 1)
1380                 return;
1381
1382         if (!gtk_tree_model_iter_nth_child(model, &selected, NULL, row))
1383                 return;
1384
1385         /* compute number of rows per page (approximation) */
1386         path = gtk_tree_model_get_path(model, &selected);
1387         gtk_tree_view_get_cell_area(GTK_TREE_VIEW(filtering.cond_list_view), path, NULL, &cell_rect);
1388         gtk_tree_view_get_visible_rect(GTK_TREE_VIEW(filtering.cond_list_view), &view_rect);
1389         gtk_tree_path_free(path);
1390         target_row = row - (view_rect.height/cell_rect.height);
1391         if (target_row < 1)
1392                 target_row = 1;
1393
1394         if (!gtk_tree_model_iter_nth_child(model, &target, NULL, target_row))
1395                 return;
1396         gtk_list_store_move_before(GTK_LIST_STORE(model), &selected, &target);
1397         gtkut_list_view_select_row(filtering.cond_list_view, target_row);
1398         modified = TRUE;
1399 }
1400 static void prefs_filtering_up(gpointer action, gpointer data)
1401 {
1402         gint row;
1403         GtkTreeIter top, sel;
1404         GtkTreeModel *model;
1405
1406         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1407         if (row <= 1) 
1408                 return;
1409                 
1410         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1411
1412         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row - 1)
1413         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
1414                 return;
1415
1416         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
1417         gtkut_list_view_select_row(filtering.cond_list_view, row - 1);
1418         modified = TRUE;
1419 }
1420
1421 static void prefs_filtering_down(gpointer action, gpointer data)
1422 {
1423         gint row, n_rows;
1424         GtkTreeIter top, sel;
1425         GtkTreeModel *model;
1426
1427         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1428         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1429         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1430         if (row < 1 || row >= n_rows - 1)
1431                 return;
1432
1433         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
1434         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row + 1))
1435                 return;
1436                         
1437         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
1438         gtkut_list_view_select_row(filtering.cond_list_view, row + 1);
1439         modified = TRUE;
1440 }
1441
1442 static void prefs_filtering_page_down(gpointer action, gpointer data)
1443 {
1444         gint row, target_row, n_rows;
1445         GtkTreeIter selected, target;
1446         GtkTreeModel *model;
1447         GtkTreePath *path;
1448         GdkRectangle cell_rect, view_rect;
1449
1450         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1451         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1452         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1453         if (row < 1 || row >= n_rows -1)
1454                 return;
1455
1456         if (!gtk_tree_model_iter_nth_child(model, &selected, NULL, row))
1457                 return;
1458
1459         /* compute number of rows per page (approximation) */
1460         path = gtk_tree_model_get_path(model, &selected);
1461         gtk_tree_view_get_cell_area(GTK_TREE_VIEW(filtering.cond_list_view), path, NULL, &cell_rect);
1462         gtk_tree_view_get_visible_rect(GTK_TREE_VIEW(filtering.cond_list_view), &view_rect);
1463         gtk_tree_path_free(path);
1464         target_row = row + (view_rect.height/cell_rect.height);
1465         if (target_row > n_rows-1)
1466                 target_row = n_rows-1;
1467
1468         if (!gtk_tree_model_iter_nth_child(model, &target, NULL, target_row))
1469                 return;
1470         gtk_list_store_move_after(GTK_LIST_STORE(model), &selected, &target);
1471         gtkut_list_view_select_row(filtering.cond_list_view, target_row);
1472         modified = TRUE;
1473 }
1474
1475 static void prefs_filtering_bottom(gpointer action, gpointer data)
1476 {
1477         gint row, n_rows;
1478         GtkTreeIter top, sel;
1479         GtkTreeModel *model;
1480
1481         model = gtk_tree_view_get_model(GTK_TREE_VIEW(filtering.cond_list_view));       
1482         n_rows = gtk_tree_model_iter_n_children(model, NULL);
1483         row = gtkut_list_view_get_selected_row(filtering.cond_list_view);
1484         if (row < 1 || row >= n_rows - 1)
1485                 return;
1486
1487         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
1488         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, n_rows - 1))
1489                 return;
1490
1491         gtk_list_store_move_after(GTK_LIST_STORE(model), &top, &sel);           
1492         gtkut_list_view_select_row(filtering.cond_list_view, n_rows - 1);
1493         modified = TRUE;
1494 }
1495
1496 static void prefs_filtering_select_set(FilteringProp *prop)
1497 {
1498         gchar *matcher_str;
1499         gchar *action_str;
1500
1501         prefs_filtering_reset_dialog();
1502
1503         matcher_str = matcherlist_to_string(prop->matchers);
1504         if (matcher_str == NULL) {
1505                 return;
1506         }
1507         
1508         if (prop->name != NULL)
1509                 gtk_entry_set_text(GTK_ENTRY(filtering.name_entry), prop->name);
1510
1511         combobox_select_by_data(GTK_COMBO_BOX(filtering.account_combobox), prop->account_id);
1512                 
1513         gtk_entry_set_text(GTK_ENTRY(filtering.cond_entry), matcher_str);
1514
1515         action_str = filteringaction_list_to_string(prop->action_list);
1516         if (matcher_str == NULL) {
1517                 return;
1518         }
1519         gtk_entry_set_text(GTK_ENTRY(filtering.action_entry), action_str);
1520
1521         g_free(action_str);
1522         g_free(matcher_str);
1523 }
1524
1525 static gint prefs_filtering_deleted(GtkWidget *widget, GdkEventAny *event,
1526                                  gpointer data)
1527 {
1528         prefs_filtering_cancel(NULL, NULL);
1529         return TRUE;
1530 }
1531
1532 static gboolean prefs_filtering_key_pressed(GtkWidget *widget, GdkEventKey *event,
1533                                      gpointer data)
1534 {
1535         if (event && event->keyval == GDK_Escape) {
1536                 prefs_filtering_cancel(NULL, NULL);
1537                 return TRUE;                    
1538         }
1539         return FALSE;
1540 }
1541
1542 static gboolean prefs_filtering_check_mod(gboolean check_changed_list)
1543 {
1544         FilteringProp * prop;
1545         gchar * str;
1546         gchar * filtering_str;
1547         gint row = 1;
1548         AlertValue val;
1549         
1550         prop = prefs_filtering_dialog_to_filtering(FALSE);
1551         
1552         if (check_changed_list) {
1553                 if (modified && alertpanel(_("Filtering rules not saved"),
1554                                          _("The list of filtering rules have been modified. Close anyway?"),
1555                                          GTK_STOCK_CLOSE, _("+_Continue editing"), 
1556                                          NULL) != G_ALERTDEFAULT) {
1557                         return TRUE;
1558                 }
1559         }
1560         
1561         /* check if a rule is being edited */
1562         if (prop != NULL) {
1563                 str = filteringprop_to_string(prop);
1564
1565                 while (NULL != (filtering_str = (prefs_filtering_list_view_get_rule
1566                                                         (filtering.cond_list_view,
1567                                                          row)))) {
1568                         if (strcmp(filtering_str, str) == 0)
1569                                 break;
1570                         row++;
1571                         g_free(filtering_str);
1572                 }       
1573
1574                 if (!filtering_str) {
1575                         val = alertpanel(_("Entry not saved"),
1576                                  _("The entry was not saved. Close anyway?"),
1577                                  GTK_STOCK_CLOSE, _("+_Continue editing"), NULL);
1578                         if (G_ALERTDEFAULT != val) {
1579                                 g_free(filtering_str);
1580                                 g_free(str); /* fixed two leaks: huzzah! */
1581                                 filteringprop_free(prop);
1582                                 return TRUE;
1583                         }
1584                 }               
1585
1586                 g_free(filtering_str);
1587                 g_free(str);
1588                 filteringprop_free(prop); /* fixed a leak: huzzah! */
1589         } else {
1590                 gchar *name, *condition, *action;
1591                 name = gtk_editable_get_chars(GTK_EDITABLE(filtering.name_entry), 0, -1);
1592                 condition = gtk_editable_get_chars(GTK_EDITABLE(filtering.cond_entry), 0, -1);
1593                 action = gtk_editable_get_chars(GTK_EDITABLE(filtering.action_entry), 0, -1);
1594                 if (strlen(name) || 
1595                     strlen(condition) || 
1596                     strlen(action)) {
1597                         val = alertpanel(_("Entry not saved"),
1598                                  _("The entry was not saved. Close anyway?"),
1599                                  GTK_STOCK_CLOSE, _("+_Continue editing"), NULL);
1600                         if (G_ALERTDEFAULT != val) {
1601                                 g_free(name);
1602                                 g_free(condition);
1603                                 g_free(action);
1604                                 return TRUE;
1605                         }
1606                 }
1607                 g_free(name);
1608                 g_free(condition);
1609                 g_free(action);
1610         }
1611         return FALSE;
1612 }
1613
1614 static void prefs_filtering_ok(gpointer action, gpointer data)
1615 {
1616         if (prefs_filtering_check_mod(FALSE))
1617                 return;
1618         modified = FALSE;
1619         prefs_filtering_set_list();
1620         prefs_matcher_write_config();
1621         prefs_filtering_close();
1622 }
1623
1624 static void prefs_filtering_cancel(gpointer action, gpointer data)
1625 {
1626         if (prefs_filtering_check_mod(TRUE))
1627                 return;
1628         modified = FALSE;
1629         prefs_matcher_read_config();
1630         prefs_filtering_close();
1631 }
1632
1633 static GtkListStore* prefs_filtering_create_data_store(void)
1634 {
1635         return gtk_list_store_new(N_PREFS_FILTERING_COLUMNS,
1636                                   G_TYPE_BOOLEAN,
1637                                   G_TYPE_STRING,
1638                                   G_TYPE_INT,
1639                                   G_TYPE_STRING,
1640                                   G_TYPE_STRING,
1641                                   G_TYPE_BOOLEAN,
1642                                  -1);
1643 }
1644
1645 /*!
1646  *\brief        Insert filtering rule into store. Note that we access the
1647  *              tree view / store by index, which is a bit suboptimal, but
1648  *              at least it made GTK 2 porting easier.
1649  *
1650  *\param        list_store Store to operate on
1651  *\param        row -1 to add a new rule to store,
1652                         row >=0 to change an existing row
1653                         row <-1 insert a new row after (-row-2)
1654  *\param        enabled TRUE if rule is enabled
1655  *\param        name The Name of rule
1656  *\param        account_id The account ID
1657  *\param        account_name The account name or All or (New)
1658  *\param        rule String representation of rule
1659  *\param        prop TRUE if valid filtering rule; if FALSE it's the first
1660  *              entry in the store ("(New)").
1661  *
1662  *\return       int Row of inserted / changed rule.
1663  */
1664 static gint prefs_filtering_list_view_insert_rule(GtkListStore *list_store,
1665                                                   gint row,
1666                                                   gboolean enabled,
1667                                                   const gchar *name,
1668                                                   gint account_id,
1669                                                   const gchar *account_name,
1670                                                   const gchar *rule,
1671                                                   gboolean prop) 
1672 {
1673         GtkTreeIter iter;
1674         GtkTreeIter sibling;
1675
1676         /* check if valid row at all */
1677         if (row >= 0) {
1678                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
1679                                                    &iter, NULL, row))
1680                         row = -1;
1681         } else if (row < -1) {
1682                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
1683                                                    &sibling, NULL, -row-2))
1684                         row = -1;                                                  
1685         }
1686
1687         if (row == -1 ) {
1688                 /* append new */
1689                 gtk_list_store_append(list_store, &iter);
1690                 gtk_list_store_set(list_store, &iter, 
1691                                    PREFS_FILTERING_ENABLED, enabled,
1692                                    PREFS_FILTERING_NAME, name,
1693                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1694                                    PREFS_FILTERING_ACCOUNT_NAME, account_name,
1695                                    PREFS_FILTERING_RULE, rule,
1696                                    PREFS_FILTERING_PROP, prop,
1697                                    -1);
1698                 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store),
1699                                                       NULL) - 1;
1700         } else if (row < -1) {
1701                 /* duplicate */
1702                 gtk_list_store_insert_after(list_store, &iter, &sibling);
1703                 gtk_list_store_set(list_store, &iter, 
1704                                    PREFS_FILTERING_ENABLED, enabled,
1705                                    PREFS_FILTERING_NAME, name,
1706                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1707                                    PREFS_FILTERING_ACCOUNT_NAME, account_name,
1708                                    PREFS_FILTERING_RULE, rule,
1709                                    PREFS_FILTERING_PROP, prop,
1710                                    -1);
1711                 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store),
1712                                                       NULL) - 1;
1713         } else {
1714                 /* change existing */
1715                 gtk_list_store_set(list_store, &iter, 
1716                                    PREFS_FILTERING_ENABLED, enabled,
1717                                    PREFS_FILTERING_NAME, name,
1718                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1719                                    PREFS_FILTERING_ACCOUNT_NAME, account_name,
1720                                    PREFS_FILTERING_RULE, rule,
1721                                    -1);
1722                 return row;                                
1723         }
1724 }
1725
1726 /*!
1727  *\return       gchar * Rule at specified row - should be freed.
1728  */
1729 static gchar *prefs_filtering_list_view_get_rule(GtkWidget *list, gint row)
1730 {       
1731         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1732         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1733         GtkTreeIter iter;
1734         gchar *result;
1735
1736         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1737                 return NULL;
1738         
1739         gtk_tree_model_get(model, &iter, 
1740                            PREFS_FILTERING_RULE, &result,
1741                            -1);
1742         
1743         return result;
1744 }
1745
1746 static void prefs_filtering_list_view_get_rule_info(GtkWidget *list, gint row,
1747                                 gboolean *enabled, gchar **name, gint *account_id)
1748 {       
1749         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1750         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1751         GtkTreeIter iter;
1752
1753         *enabled = TRUE;
1754         *name = NULL;
1755
1756         if (gtk_tree_model_iter_nth_child(model, &iter, NULL, row)) {
1757                 gtk_tree_model_get(model, &iter, 
1758                                    PREFS_FILTERING_ENABLED, enabled,
1759                                    PREFS_FILTERING_NAME, name,
1760                                    PREFS_FILTERING_ACCOUNT_ID, account_id,
1761                                    -1);
1762         }
1763 }
1764
1765 static GtkActionGroup *prefs_filtering_popup_action = NULL;
1766 static GtkWidget *prefs_filtering_popup_menu = NULL;
1767
1768 static GtkActionEntry prefs_filtering_popup_entries[] =
1769 {
1770         {"PrefsFilteringPopup",                 NULL, "PrefsFilteringPopup" },
1771         {"PrefsFilteringPopup/Delete",          NULL, N_("_Delete"), NULL, NULL, G_CALLBACK(prefs_filtering_delete_cb) },
1772         {"PrefsFilteringPopup/DeleteAll",       NULL, N_("Delete _all"), NULL, NULL, G_CALLBACK(prefs_filtering_delete_all_cb) },
1773         {"PrefsFilteringPopup/Duplicate",       NULL, N_("D_uplicate"), NULL, NULL, G_CALLBACK(prefs_filtering_duplicate_cb) },
1774 #ifdef GENERIC_UMPC
1775         {"PrefsFilteringPopup/---",             NULL, "---", NULL, NULL, NULL },
1776         {"PrefsFilteringPopup/PageUp",          NULL, N_("Move one page up"), NULL, NULL, G_CALLBACK(prefs_filtering_page_up) },
1777         {"PrefsFilteringPopup/PageDown",        NULL, N_("Move one page down"), NULL, NULL, G_CALLBACK(prefs_filtering_page_down) },
1778 #endif
1779 };
1780
1781 static gint prefs_filtering_list_btn_pressed(GtkWidget *widget, GdkEventButton *event,
1782                                     GtkTreeView *list_view)
1783 {
1784         if (event) {
1785                 /* left- or right-button click */
1786                 if (event->button == 1 || event->button == 3) {
1787                         GtkTreePath *path = NULL;
1788
1789                         if (gtk_tree_view_get_path_at_pos( list_view, event->x, event->y,
1790                                                                 &path, NULL, NULL, NULL)) {
1791                                 prefs_filtering_select_row(list_view, path);
1792                         }
1793                         if (path)
1794                                 gtk_tree_path_free(path);
1795                 }
1796
1797                 /* right-button click */
1798                 if (event->button == 3) {
1799                         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1800                         GtkTreeIter iter;
1801                         gboolean non_empty;
1802                         gint row;
1803
1804                         if (!prefs_filtering_popup_menu) {
1805                                 prefs_filtering_popup_action = cm_menu_create_action_group("PrefsFilteringPopup", prefs_filtering_popup_entries,
1806                                         G_N_ELEMENTS(prefs_filtering_popup_entries), (gpointer)list_view);
1807                                 MENUITEM_ADDUI("/Menus", "PrefsFilteringPopup", "PrefsFilteringPopup", GTK_UI_MANAGER_MENU)
1808                                 MENUITEM_ADDUI("/Menus/PrefsFilteringPopup", "Delete", "PrefsFilteringPopup/Delete", GTK_UI_MANAGER_MENUITEM)
1809                                 MENUITEM_ADDUI("/Menus/PrefsFilteringPopup", "DeleteAll", "PrefsFilteringPopup/DeleteAll", GTK_UI_MANAGER_MENUITEM)
1810                                 MENUITEM_ADDUI("/Menus/PrefsFilteringPopup", "Duplicate", "PrefsFilteringPopup/Duplicate", GTK_UI_MANAGER_MENUITEM)
1811 #ifdef GENERIC_UMPC
1812                                 MENUITEM_ADDUI("/Menus/PrefsFilteringPopup", "Separator1", "PrefsFilteringPopup/---", GTK_UI_MANAGER_SEPARATOR)
1813                                 MENUITEM_ADDUI("/Menus/PrefsFilteringPopup", "PageUp", "PrefsFilteringPopup/PageUp", GTK_UI_MANAGER_MENUITEM)
1814                                 MENUITEM_ADDUI("/Menus/PrefsFilteringPopup", "PageDown", "PrefsFilteringPopup/PageDown", GTK_UI_MANAGER_MENUITEM)
1815 #endif
1816                                 prefs_filtering_popup_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
1817                                         gtk_ui_manager_get_widget(gtkut_ui_manager(), "/Menus/PrefsFilteringPopup")) );
1818                         }
1819
1820                         /* grey out some popup menu items if there is no selected row */
1821                         row = gtkut_list_view_get_selected_row(GTK_WIDGET(list_view));
1822                         cm_menu_set_sensitive("PrefsFilteringPopup/Delete", (row > 0));
1823                         cm_menu_set_sensitive("PrefsFilteringPopup/Duplicate", (row > 0));
1824
1825                         /* grey out seom popup menu items if there is no row
1826                            (not counting the (New) one at row 0) */
1827                         non_empty = gtk_tree_model_get_iter_first(model, &iter);
1828                         if (non_empty)
1829                                 non_empty = gtk_tree_model_iter_next(model, &iter);
1830                         cm_menu_set_sensitive("PrefsFilteringPopup/DeleteAll", non_empty);
1831
1832                         gtk_menu_popup(GTK_MENU(prefs_filtering_popup_menu), 
1833                                    NULL, NULL, NULL, NULL, 
1834                                    event->button, event->time);
1835                 }
1836         }
1837         return FALSE;
1838 }
1839
1840 static gboolean prefs_filtering_list_popup_menu(GtkWidget *widget, gpointer data)
1841 {
1842         GtkTreeView *list_view = (GtkTreeView *)data;
1843         GdkEventButton event;
1844         
1845         event.button = 3;
1846         event.time = gtk_get_current_event_time();
1847         
1848         prefs_filtering_list_btn_pressed(NULL, &event, list_view);
1849
1850         return TRUE;
1851 }
1852
1853 /*!
1854  *\brief        Create list view for filtering
1855  */
1856 static GtkWidget *prefs_filtering_list_view_create(void)
1857 {
1858         GtkTreeView *list_view;
1859         GtkTreeSelection *selector;
1860
1861         list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL
1862                 (prefs_filtering_create_data_store())));
1863 #ifdef GENERIC_UMPC
1864         g_object_set(list_view, "allow-checkbox-mode", FALSE, NULL);
1865 #endif
1866
1867 #ifndef MAEMO
1868         g_signal_connect(G_OBJECT(list_view), "popup-menu",
1869                          G_CALLBACK(prefs_filtering_list_popup_menu), list_view);
1870 #else
1871         gtk_widget_tap_and_hold_setup(GTK_WIDGET(list_view), NULL, NULL,
1872                         GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
1873         g_signal_connect(G_OBJECT(list_view), "tap-and-hold",
1874                          G_CALLBACK(prefs_filtering_list_popup_menu), list_view);
1875 #endif
1876         g_signal_connect(G_OBJECT(list_view), "button-press-event",
1877                         G_CALLBACK(prefs_filtering_list_btn_pressed), list_view);
1878         
1879         gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
1880         gtk_tree_view_set_reorderable(list_view, TRUE);
1881         
1882         selector = gtk_tree_view_get_selection(list_view);
1883         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
1884
1885         /* create the columns */
1886         prefs_filtering_create_list_view_columns(GTK_WIDGET(list_view));
1887
1888         return GTK_WIDGET(list_view);
1889 }
1890
1891 static void prefs_filtering_enable_toggled(GtkCellRendererToggle *widget,
1892                 gchar *path,
1893                 GtkWidget *list_view)
1894 {
1895         GtkTreeIter iter;
1896         GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list_view));
1897         gboolean enabled = TRUE;
1898
1899         if (!gtk_tree_model_get_iter_from_string(model, &iter, path))
1900                 return;
1901
1902         gtk_tree_model_get(model, &iter,
1903                            PREFS_FILTERING_ENABLED, &enabled,
1904                            -1);
1905
1906         gtk_list_store_set(GTK_LIST_STORE(model), &iter,
1907                            PREFS_FILTERING_ENABLED, !enabled,
1908                            -1);
1909 }
1910
1911 static void prefs_filtering_create_list_view_columns(GtkWidget *list_view)
1912 {
1913         GtkTreeViewColumn *column;
1914         GtkCellRenderer *renderer;
1915
1916         renderer = gtk_cell_renderer_toggle_new();
1917         g_object_set(renderer,
1918                      "radio", FALSE,
1919                      "activatable", TRUE,
1920                      NULL);
1921         column = gtk_tree_view_column_new_with_attributes
1922                 (_("Enable"), /* FIXME : Enable, Enabled, or 'E' ? */
1923                  renderer,
1924                  "active", PREFS_FILTERING_ENABLED,
1925                  NULL);
1926         gtk_tree_view_column_set_alignment (column, 0.5);
1927         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1928         g_signal_connect(G_OBJECT(renderer), "toggled",
1929                          G_CALLBACK(prefs_filtering_enable_toggled),
1930                          list_view);
1931
1932         renderer = gtk_cell_renderer_text_new();
1933         column = gtk_tree_view_column_new_with_attributes
1934                 (_("Name"),
1935                  renderer,
1936                  "text", PREFS_FILTERING_NAME,
1937                  NULL);
1938         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1939         gtk_tree_view_column_set_resizable(column, TRUE);
1940
1941         renderer = gtk_cell_renderer_text_new();
1942         column = gtk_tree_view_column_new_with_attributes
1943                 (_("Account"),
1944                  renderer,
1945                  "text", PREFS_FILTERING_ACCOUNT_NAME,
1946                  NULL);
1947         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
1948         gtk_tree_view_column_set_resizable(column, TRUE);
1949
1950         filtering.account_name_column = column;
1951                 
1952         renderer = gtk_cell_renderer_text_new();
1953         column = gtk_tree_view_column_new_with_attributes
1954                 (_("Rule"),
1955                  renderer,
1956                  "text", PREFS_FILTERING_RULE,
1957                  NULL);
1958
1959         gtk_tree_view_set_search_column(GTK_TREE_VIEW(list_view), PREFS_FILTERING_NAME);
1960         gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(list_view), prefs_filtering_search_func_cb , NULL, NULL);
1961         
1962         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
1963 }
1964
1965 /*!
1966  *\brief        Triggered when a row has to be selected
1967  */
1968 static void prefs_filtering_select_row(GtkTreeView *list_view, GtkTreePath *path)
1969 {
1970         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1971
1972         if (path && model) {
1973                 GtkTreeSelection *selection;
1974                 gboolean has_prop = FALSE;
1975                 GtkTreeIter iter;
1976
1977                 /* select row */
1978                 selection = gtk_tree_view_get_selection(list_view);
1979                 gtk_tree_selection_select_path(selection, path);
1980
1981                 /* update dialog from selection */
1982                 gtk_tree_model_get_iter(model, &iter, path);
1983                 gtk_tree_model_get(model, &iter,
1984                                    PREFS_FILTERING_PROP, &has_prop,
1985                                    -1);
1986
1987                 if (has_prop) {
1988                         FilteringProp *prop;
1989                         gchar *filtering_str = NULL;
1990                         gchar *name = NULL;
1991                         gint account_id = 0;
1992
1993                         gtk_tree_model_get(model, &iter,
1994                                            PREFS_FILTERING_RULE, &filtering_str,
1995                                            -1);
1996                         gtk_tree_model_get(model, &iter,
1997                                            PREFS_FILTERING_NAME, &name,
1998                                            -1);
1999                         gtk_tree_model_get(model, &iter,
2000                                            PREFS_FILTERING_ACCOUNT_ID, &account_id,
2001                                            -1);
2002
2003                         prop = matcher_parser_get_filtering(filtering_str);
2004                         if (prop) {
2005                                 prop->name = g_strdup(name);
2006                                 prop->account_id = account_id;
2007                                 prefs_filtering_select_set(prop);
2008                                 filteringprop_free(prop);
2009                         }                               
2010                         g_free(name);
2011                         g_free(filtering_str);
2012                 } else
2013                         prefs_filtering_reset_dialog();
2014         }
2015 }