2007-07-27 [colin] 2.10.0cvs72
[claws.git] / src / prefs_matcher.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29 #include <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_common.h"
39 #include "mainwindow.h"
40 #include "foldersel.h"
41 #include "manage_window.h"
42 #include "inc.h"
43 #include "matcher.h"
44 #include "utils.h"
45 #include "gtkutils.h"
46 #include "alertpanel.h"
47 #include "folder.h"
48 #include "description_window.h"
49
50 #include "matcher_parser.h"
51 #include "colorlabel.h"
52
53 enum {
54         PREFS_MATCHER_COND,
55         PREFS_MATCHER_COND_VALID,
56         N_PREFS_MATCHER_COLUMNS
57 };
58
59 /*!
60  *\brief        UI data for matcher dialog
61  */
62 static struct Matcher {
63         GtkWidget *window;
64
65         GtkWidget *ok_btn;
66
67         GtkWidget *predicate_combo;
68         GtkWidget *predicate_flag_combo;
69         GtkWidget *header_combo;
70         GtkWidget *header_addr_combo;
71
72         GtkWidget *criteria_list;
73
74         GtkWidget *predicate_list;
75         GtkWidget *predicate_label;
76         GtkWidget *predicate_flag_list;
77
78         GtkWidget *bool_op_list;
79
80         GtkWidget *header_entry;
81         GtkWidget *header_label;
82         GtkWidget *header_addr_entry;
83         GtkWidget *header_addr_label;
84         GtkWidget *value_entry;
85         GtkWidget *value_label;
86         GtkWidget *addressbook_folder_label;
87         GtkWidget *addressbook_folder_combo;
88         GtkWidget *case_checkbtn;
89         GtkWidget *regexp_checkbtn;
90         GtkWidget *color_optmenu;
91
92         GtkWidget *test_btn;
93         GtkWidget *addressbook_select_btn;
94
95         GtkWidget *cond_list_view;
96
97         GtkWidget *criteria_table;
98
99         gint selected_criteria; /*!< selected criteria in combobox */ 
100 } matcher;
101
102 /*!
103  *\brief        Conditions with a negate counterpart (like unread and ~unread)
104  *              have the same CRITERIA_XXX id). I.e. both unread and ~unread
105  *              have criteria id CRITERIA_UNREAD. This id is passed as the
106  *              first parameter to #matcherprop_new and #matcherprop_unquote_new.
107  *
108  *\warning      Currently the enum constants should have the same order as the 
109  *              #criteria_text  
110  */             
111 enum {
112         CRITERIA_ALL = 0,
113
114         CRITERIA_SUBJECT = 1,
115         CRITERIA_FROM = 2,
116         CRITERIA_TO = 3,
117         CRITERIA_CC = 4,
118         CRITERIA_TO_OR_CC = 5,
119         CRITERIA_NEWSGROUPS = 6,
120         CRITERIA_INREPLYTO = 7,
121         CRITERIA_REFERENCES = 8,
122         CRITERIA_AGE_GREATER = 9,
123         CRITERIA_AGE_LOWER = 10,
124         CRITERIA_HEADER = 11,
125         CRITERIA_HEADERS_PART = 12,
126         CRITERIA_BODY_PART = 13,
127         CRITERIA_MESSAGE = 14,
128
129         CRITERIA_UNREAD = 15,
130         CRITERIA_NEW = 16,
131         CRITERIA_MARKED = 17,
132         CRITERIA_DELETED = 18,
133         CRITERIA_REPLIED = 19,
134         CRITERIA_FORWARDED = 20,
135         CRITERIA_LOCKED = 21,
136         CRITERIA_SPAM = 22,
137         CRITERIA_COLORLABEL = 23,
138         CRITERIA_IGNORE_THREAD = 24,
139
140         CRITERIA_SCORE_GREATER = 25,
141         CRITERIA_SCORE_LOWER = 26,
142         CRITERIA_SCORE_EQUAL = 27,
143
144         CRITERIA_TEST = 28,
145
146         CRITERIA_SIZE_GREATER = 29,
147         CRITERIA_SIZE_SMALLER = 30,
148         CRITERIA_SIZE_EQUAL   = 31,
149         
150         CRITERIA_PARTIAL = 32,
151
152         CRITERIA_FOUND_IN_ADDRESSBOOK = 33,
153         
154         CRITERIA_TAG = 34,
155         CRITERIA_TAGGED = 35
156 };
157
158 /*!
159  *\brief        Descriptive text for conditions
160  */
161 typedef struct _struct_criteria_text struct_criteria_text;
162 struct _struct_criteria_text {
163         const gchar     *text;
164         gboolean         contains_header_name;
165         /* if contains_header_name is TRUE, prefs_common_translated_headername(text)
166            will be used
167         */
168 };
169
170 static struct_criteria_text criteria_text [] = {
171         { N_("All messages"), FALSE },
172         { N_("Subject"), TRUE },
173         { N_("From"), TRUE },
174         { N_("To"), TRUE },
175         { N_("Cc"), TRUE },
176         { N_("To or Cc"), TRUE },
177         { N_("Newsgroups"), TRUE },
178         { N_("In reply to"), TRUE },
179         { N_("References"), TRUE },
180         { N_("Age greater than"), FALSE },
181         { N_("Age lower than"), FALSE },
182         { N_("Header"), FALSE },
183         { N_("Headers part"), FALSE },
184         { N_("Body part"), FALSE },
185         { N_("Whole message"), FALSE },
186         { N_("Unread flag"), FALSE },
187         { N_("New flag"), FALSE },
188         { N_("Marked flag"), FALSE },
189         { N_("Deleted flag"), FALSE },
190         { N_("Replied flag"), FALSE },
191         { N_("Forwarded flag"), FALSE },
192         { N_("Locked flag"), FALSE },
193         { N_("Spam flag"), FALSE },
194         { N_("Color label"), FALSE },
195         { N_("Ignored thread"), FALSE },
196         { N_("Score greater than"), FALSE },
197         { N_("Score lower than"), FALSE },
198         { N_("Score equal to"), FALSE },
199         { N_("Test"), FALSE },
200         { N_("Size greater than"), FALSE }, 
201         { N_("Size smaller than"), FALSE },
202         { N_("Size exactly"), FALSE },
203         { N_("Partially downloaded"), FALSE },
204         { N_("Found in addressbook"), FALSE },
205         { N_("Tags"), FALSE },
206         { N_("Tagged"), FALSE },
207         { NULL, FALSE }
208 };
209
210 /*!
211  *\brief        Boolean / predicate constants
212  *
213  *\warning      Same order as #bool_op_text!
214  */
215 enum {
216         BOOL_OP_OR = 0,
217         BOOL_OP_AND = 1
218 };
219
220 /*!
221  *\brief        Descriptive text in UI
222  */
223 static const gchar *bool_op_text [] = {
224         N_("or"), N_("and")
225 };
226
227 /*!
228  *\brief        Contains predicate      
229  *
230  *\warning      Same order as in #predicate_text
231  */
232 enum {
233         PREDICATE_CONTAINS = 0,
234         PREDICATE_DOES_NOT_CONTAIN = 1
235 };
236
237 /*!
238  *\brief        Descriptive text in UI for predicate
239  */
240 static const gchar *predicate_text [] = {
241         N_("contains"), N_("does not contain")
242 };
243
244 /*!
245  *\brief        Preset addressbook book/folder items
246  */
247 static const gchar *addressbook_folder_text [] = {
248         N_("Any")
249 };
250
251 /*!
252  *\brief        Enabled predicate
253  *
254  *\warning      Same order as in #predicate_flag_text
255  */
256 enum {
257         PREDICATE_FLAG_ENABLED = 0,
258         PREDICATE_FLAG_DISABLED = 1
259 };
260
261 /*!
262  *\brief        Descriptive text in UI for enabled flag
263  */
264 static const gchar *predicate_flag_text [] = {
265         N_("yes"), N_("no")
266 };
267
268 /*!
269  *\brief        Hooks
270  */
271 static PrefsMatcherSignal *matchers_callback;
272
273 /* widget creating functions */
274 static void prefs_matcher_create        (void);
275
276 static void prefs_matcher_set_dialog    (MatcherList *matchers);
277 static void prefs_matcher_list_view_set_row     (GtkTreeIter *row, 
278                                                  MatcherProp *prop);
279
280 /* callback functions */
281
282 static void prefs_matcher_register_cb   (void);
283 static void prefs_matcher_substitute_cb (void);
284 static void prefs_matcher_delete_cb     (void);
285 static void prefs_matcher_up            (void);
286 static void prefs_matcher_down          (void);
287 static gboolean prefs_matcher_key_pressed(GtkWidget     *widget,
288                                          GdkEventKey    *event,
289                                          gpointer        data);
290 static void prefs_matcher_ok            (void);
291 static void prefs_matcher_cancel        (void);
292 static gint prefs_matcher_deleted       (GtkWidget *widget, GdkEventAny *event,
293                                          gpointer data);
294 static void prefs_matcher_criteria_select       (GtkList   *list,
295                                                  GtkWidget *widget,
296                                                  gpointer   user_data);
297 static MatcherList *prefs_matcher_get_list      (void);
298
299 static GtkListStore* prefs_matcher_create_data_store    (void);
300
301 static void prefs_matcher_list_view_insert_matcher      (GtkWidget *list_view,
302                                                          GtkTreeIter *row_iter,
303                                                          const gchar *matcher,
304                                                          gboolean is_valid);
305
306 static GtkWidget *prefs_matcher_list_view_create        (void);
307
308 static void prefs_matcher_create_list_view_columns      (GtkWidget *list_view);
309
310 static gboolean prefs_matcher_selected                  (GtkTreeSelection *selector,
311                                                          GtkTreeModel *model, 
312                                                          GtkTreePath *path,
313                                                          gboolean currently_selected,
314                                                          gpointer data);
315
316 /*!
317  *\brief        Find index of list selection 
318  *
319  *\param        list GTK list widget
320  *
321  *\return       gint Selection index
322  */
323 static gint get_sel_from_list(GtkList *list)
324 {
325         gint row = 0;
326         void * sel;
327         GList * child;
328
329         sel = list->selection->data;
330         for (child = list->children; child != NULL; child = g_list_next(child)) {
331                 if (child->data == sel)
332                         return row;
333                 row ++;
334         }
335         
336         return row;
337 }
338
339 /*!
340  *\brief        Opens the matcher dialog with a list of conditions
341  *
342  *\param        matchers List of conditions
343  *\param        cb Callback
344  *
345  */
346 void prefs_matcher_open(MatcherList *matchers, PrefsMatcherSignal *cb)
347 {
348         inc_lock();
349
350         if (!matcher.window) {
351                 prefs_matcher_create();
352         } else {
353                 /* update color label menu */
354                 gtk_option_menu_set_menu(GTK_OPTION_MENU(matcher.color_optmenu),
355                                 colorlabel_create_color_menu());
356         }
357
358         manage_window_set_transient(GTK_WINDOW(matcher.window));
359         gtk_widget_grab_focus(matcher.ok_btn);
360
361         matchers_callback = cb;
362
363         prefs_matcher_set_dialog(matchers);
364
365         gtk_widget_show(matcher.window);
366 }
367
368 /*!
369  *\brief        Save Gtk object size to prefs dataset
370  */
371 static void prefs_matcher_size_allocate_cb(GtkWidget *widget,
372                                          GtkAllocation *allocation)
373 {
374         g_return_if_fail(allocation != NULL);
375
376         prefs_common.matcherwin_width = allocation->width;
377         prefs_common.matcherwin_height = allocation->height;
378 }
379
380 /*!
381  *\brief        Create the matcher dialog
382  */
383 static void prefs_matcher_create(void)
384 {
385         GtkWidget *window;
386         GtkWidget *vbox;
387         GtkWidget *ok_btn;
388         GtkWidget *cancel_btn;
389         GtkWidget *confirm_area;
390
391         GtkWidget *vbox1;
392         GtkWidget *vbox2;
393         GtkWidget *vbox3;
394         GtkWidget *criteria_table;
395
396         GtkWidget *hbox1;
397
398         GtkWidget *header_combo;
399         GtkWidget *header_entry;
400         GtkWidget *header_label;
401         GtkWidget *header_addr_combo;
402         GtkWidget *header_addr_entry;
403         GtkWidget *header_addr_label;
404         GtkWidget *criteria_combo;
405         GtkWidget *criteria_list;
406         GtkWidget *criteria_label;
407         GtkWidget *value_label;
408         GtkWidget *value_entry;
409         GtkWidget *addressbook_folder_label;
410         GtkWidget *addressbook_folder_combo;
411         GtkWidget *predicate_combo;
412         GtkWidget *predicate_list;
413         GtkWidget *predicate_flag_combo;
414         GtkWidget *predicate_flag_list;
415         GtkWidget *predicate_label;
416         GtkWidget *bool_op_combo;
417         GtkWidget *bool_op_list;
418         GtkWidget *bool_op_label;
419
420         GtkWidget *regexp_checkbtn;
421         GtkWidget *case_checkbtn;
422
423         GtkWidget *reg_hbox;
424         GtkWidget *btn_hbox;
425         GtkWidget *arrow;
426         GtkWidget *reg_btn;
427         GtkWidget *subst_btn;
428         GtkWidget *del_btn;
429
430         GtkWidget *cond_hbox;
431         GtkWidget *cond_scrolledwin;
432         GtkWidget *cond_list_view;
433
434         GtkWidget *btn_vbox;
435         GtkWidget *up_btn;
436         GtkWidget *down_btn;
437
438         GtkWidget *test_btn;
439         GtkWidget *addressbook_select_btn;
440
441         GtkWidget *color_optmenu;
442
443         GList *combo_items;
444         gint i;
445         static GdkGeometry geometry;
446
447         debug_print("Creating matcher configuration window...\n");
448
449         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "prefs_matcher");
450         gtk_container_set_border_width(GTK_CONTAINER(window), 8);
451         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
452         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
453         gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
454
455         vbox = gtk_vbox_new(FALSE, 6);
456         gtk_widget_show(vbox);
457         gtk_container_add(GTK_CONTAINER(window), vbox);
458
459         gtkut_stock_button_set_create(&confirm_area, &cancel_btn, GTK_STOCK_CANCEL,
460                                       &ok_btn, GTK_STOCK_OK, NULL, NULL);
461         gtk_widget_show(confirm_area);
462         gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
463         gtk_widget_grab_default(ok_btn);
464
465         gtk_window_set_title(GTK_WINDOW(window),
466                              _("Condition configuration"));
467         g_signal_connect(G_OBJECT(window), "delete_event",
468                          G_CALLBACK(prefs_matcher_deleted), NULL);
469         g_signal_connect(G_OBJECT(window), "size_allocate",
470                          G_CALLBACK(prefs_matcher_size_allocate_cb), NULL);
471         g_signal_connect(G_OBJECT(window), "key_press_event",
472                          G_CALLBACK(prefs_matcher_key_pressed), NULL);
473         MANAGE_WINDOW_SIGNALS_CONNECT(window);
474         g_signal_connect(G_OBJECT(ok_btn), "clicked",
475                          G_CALLBACK(prefs_matcher_ok), NULL);
476         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
477                          G_CALLBACK(prefs_matcher_cancel), NULL);
478
479         vbox1 = gtk_vbox_new(FALSE, VSPACING);
480         gtk_widget_show(vbox1);
481         gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0);
482         gtk_container_set_border_width(GTK_CONTAINER (vbox1), 2);
483
484         criteria_table = gtk_table_new(2, 4, FALSE);
485         gtk_widget_show(criteria_table);
486
487         gtk_box_pack_start(GTK_BOX(vbox1), criteria_table, FALSE, FALSE, 0);
488         gtk_table_set_row_spacings(GTK_TABLE(criteria_table), 8);
489         gtk_table_set_col_spacings(GTK_TABLE(criteria_table), 8);
490
491         /* criteria combo box */
492
493         criteria_label = gtk_label_new(_("Match type"));
494         gtk_widget_show(criteria_label);
495         gtk_misc_set_alignment(GTK_MISC(criteria_label), 0, 0.5);
496         gtk_table_attach(GTK_TABLE(criteria_table), criteria_label, 0, 1, 0, 1,
497                          GTK_FILL, 0, 0, 0);
498
499         criteria_combo = gtk_combo_new();
500         gtk_widget_show(criteria_combo);
501
502         combo_items = NULL;
503
504         for (i = 0; criteria_text[i].text != NULL; i++) {
505                 if (criteria_text[i].contains_header_name)
506                         combo_items = g_list_append(combo_items,
507                                             (gpointer) prefs_common_translated_header_name(criteria_text[i].text));
508                 else
509                         combo_items = g_list_append(combo_items,
510                                             (gpointer) gettext(criteria_text[i].text));
511         }
512         gtk_combo_set_popdown_strings(GTK_COMBO(criteria_combo), combo_items);
513
514         g_list_free(combo_items);
515
516         gtk_widget_set_size_request(criteria_combo, 170, -1);
517         gtk_table_attach(GTK_TABLE(criteria_table), criteria_combo, 0, 1, 1, 2,
518                           0, 0, 0, 0);
519         criteria_list = GTK_COMBO(criteria_combo)->list;
520         g_signal_connect(G_OBJECT(criteria_list), "select-child",
521                          G_CALLBACK(prefs_matcher_criteria_select),
522                          NULL);
523
524         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(criteria_combo)->entry),
525                                FALSE);
526
527         /* header name */
528
529         header_label = gtk_label_new(_("Header name"));
530         gtk_widget_show(header_label);
531         gtk_misc_set_alignment(GTK_MISC(header_label), 0, 0.5);
532         gtk_table_attach(GTK_TABLE(criteria_table), header_label, 1, 2, 0, 1,
533                          GTK_FILL, 0, 0, 0);
534
535         header_combo = gtk_combo_new();
536         gtk_widget_show(header_combo);
537         gtk_widget_set_size_request(header_combo, 120, -1);
538         gtkut_combo_set_items(GTK_COMBO (header_combo),
539                               "Subject", "From", "To", "Cc", "Reply-To",
540                               "Sender", "X-ML-Name", "X-List", "X-Sequence",
541                               "X-Mailer","X-BeenThere",
542                               NULL);
543         gtk_table_attach(GTK_TABLE(criteria_table), header_combo, 1, 2, 1, 2,
544                          0, 0, 0, 0);
545         header_entry = GTK_COMBO(header_combo)->entry;
546         gtk_entry_set_editable(GTK_ENTRY(header_entry), TRUE);
547
548         /* address header name */
549
550         header_addr_label = gtk_label_new(_("Address header"));
551         gtk_misc_set_alignment(GTK_MISC(header_addr_label), 0, 0.5);
552         gtk_table_attach(GTK_TABLE(criteria_table), header_addr_label, 1, 2, 0, 1,
553                          GTK_FILL, 0, 0, 0);
554
555         header_addr_combo = gtk_combo_new();
556         gtk_widget_set_size_request(header_addr_combo, 120, -1);
557         gtkut_combo_set_items(GTK_COMBO (header_addr_combo),
558                               Q_("Filtering Matcher Menu|All"),
559                               _("Any"), "From", "To", "Cc", "Reply-To", "Sender",
560                               NULL);
561         gtk_table_attach(GTK_TABLE(criteria_table), header_addr_combo, 1, 2, 1, 2,
562                          0, 0, 0, 0);
563         header_addr_entry = GTK_COMBO(header_addr_combo)->entry;
564         gtk_entry_set_editable(GTK_ENTRY(header_addr_entry), TRUE);
565
566         /* value */
567
568         value_label = gtk_label_new(_("Value"));
569         gtk_widget_show(value_label);
570         gtk_misc_set_alignment(GTK_MISC (value_label), 0, 0.5);
571         gtk_table_attach(GTK_TABLE(criteria_table), value_label, 2, 3, 0, 1,
572                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
573
574         value_entry = gtk_entry_new();
575         gtk_widget_show(value_entry);
576         gtk_widget_set_size_request(value_entry, 200, -1);
577         gtk_table_attach(GTK_TABLE(criteria_table), value_entry, 2, 3, 1, 2,
578                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
579
580         /* book/folder value */
581
582         addressbook_folder_label = gtk_label_new(_("Book/folder"));
583         gtk_misc_set_alignment(GTK_MISC (addressbook_folder_label), 0, 0.5);
584         gtk_table_attach(GTK_TABLE(criteria_table), addressbook_folder_label, 2, 3, 0, 1,
585                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
586
587         addressbook_folder_combo = gtk_combo_new();
588         gtk_widget_show(addressbook_folder_combo);
589         gtk_widget_set_size_request(addressbook_folder_combo, 200, -1);
590         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(addressbook_folder_combo)->entry),
591                                TRUE);
592
593         combo_items = NULL;
594         for (i = 0; i < (gint) (sizeof(addressbook_folder_text) / sizeof(gchar *)); i++) {
595                 combo_items = g_list_append(combo_items,
596                                             (gpointer) _(addressbook_folder_text[i]));
597         }
598         gtk_combo_set_popdown_strings(GTK_COMBO(addressbook_folder_combo), combo_items);
599         g_list_free(combo_items);
600
601         gtk_table_attach(GTK_TABLE(criteria_table), addressbook_folder_combo, 2, 3, 1, 2,
602                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
603
604 #if GTK_CHECK_VERSION(2, 8, 0)
605         test_btn = gtk_button_new_from_stock(GTK_STOCK_INFO);
606 #else
607         test_btn = gtk_button_new_with_label(_("Info..."));
608 #endif
609         gtk_widget_show(test_btn);
610         gtk_table_attach(GTK_TABLE (criteria_table), test_btn, 3, 4, 1, 2,
611                          0, 0, 0, 0);
612         g_signal_connect(G_OBJECT (test_btn), "clicked",
613                          G_CALLBACK(prefs_matcher_test_info),
614                          NULL);
615
616         addressbook_select_btn = gtk_button_new_with_label(_("Select ..."));
617         gtk_table_attach(GTK_TABLE (criteria_table), addressbook_select_btn, 3, 4, 1, 2,
618                          0, 0, 0, 0);
619         g_signal_connect(G_OBJECT (addressbook_select_btn), "clicked",
620                          G_CALLBACK(prefs_matcher_addressbook_select),
621                          NULL);
622
623         color_optmenu = gtk_option_menu_new();
624         gtk_option_menu_set_menu(GTK_OPTION_MENU(color_optmenu),
625                                  colorlabel_create_color_menu());
626
627         /* predicate */
628
629         vbox2 = gtk_vbox_new(FALSE, VSPACING);
630         gtk_widget_show(vbox2);
631         gtk_box_pack_start(GTK_BOX(vbox1), vbox2, FALSE, FALSE, 0);
632
633         hbox1 = gtk_hbox_new(FALSE, 8);
634         gtk_widget_show(hbox1);
635         gtk_box_pack_start(GTK_BOX(vbox2), hbox1, FALSE, FALSE, 0);
636
637         predicate_label = gtk_label_new(_("Predicate"));
638         gtk_widget_show(predicate_label);
639         gtk_box_pack_start(GTK_BOX(hbox1), predicate_label,
640                            FALSE, FALSE, 0);
641
642         predicate_combo = gtk_combo_new();
643         gtk_widget_show(predicate_combo);
644         gtk_widget_set_size_request(predicate_combo, 120, -1);
645         predicate_list = GTK_COMBO(predicate_combo)->list;
646         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(predicate_combo)->entry),
647                                FALSE);
648
649         combo_items = NULL;
650
651         for (i = 0; i < (gint) (sizeof(predicate_text) / sizeof(gchar *)); i++) {
652                 combo_items = g_list_append(combo_items,
653                                             (gpointer) _(predicate_text[i]));
654         }
655         gtk_combo_set_popdown_strings(GTK_COMBO(predicate_combo), combo_items);
656
657         g_list_free(combo_items);
658
659         gtk_box_pack_start(GTK_BOX(hbox1), predicate_combo,
660                            FALSE, FALSE, 0);
661
662         /* predicate flag */
663
664         predicate_flag_combo = gtk_combo_new();
665         gtk_widget_hide(predicate_flag_combo);
666         gtk_widget_set_size_request(predicate_flag_combo, 120, -1);
667         predicate_flag_list = GTK_COMBO(predicate_flag_combo)->list;
668         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(predicate_flag_combo)->entry), FALSE);
669
670         combo_items = NULL;
671
672         for (i = 0; i < (gint) (sizeof(predicate_text) / sizeof(gchar *)); i++) {
673                 combo_items = g_list_append(combo_items, (gpointer) _(predicate_flag_text[i]));
674         }
675         gtk_combo_set_popdown_strings(GTK_COMBO(predicate_flag_combo),
676                                       combo_items);
677
678         g_list_free(combo_items);
679
680         gtk_box_pack_start(GTK_BOX(hbox1), predicate_flag_combo,
681                            FALSE, FALSE, 0);
682
683         vbox3 = gtk_vbox_new(FALSE, 0);
684         gtk_widget_show (vbox3);
685         gtk_box_pack_start(GTK_BOX(hbox1), vbox3, FALSE, FALSE, 0);
686
687         PACK_CHECK_BUTTON(vbox3, case_checkbtn, _("Case sensitive"));
688         PACK_CHECK_BUTTON(vbox3, regexp_checkbtn, _("Use regexp"));
689
690         /* register / substitute / delete */
691
692         reg_hbox = gtk_hbox_new(FALSE, 4);
693         gtk_widget_show(reg_hbox);
694         gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0);
695
696         arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
697         gtk_widget_show(arrow);
698         gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0);
699         gtk_widget_set_size_request(arrow, -1, 16);
700
701         btn_hbox = gtk_hbox_new(FALSE, 4);
702         gtk_widget_show(btn_hbox);
703         gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0);
704
705         reg_btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
706         gtk_widget_show(reg_btn);
707         gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0);
708         g_signal_connect(G_OBJECT(reg_btn), "clicked",
709                          G_CALLBACK(prefs_matcher_register_cb), NULL);
710
711         subst_btn = gtkut_get_replace_btn(_("Replace"));
712         gtk_widget_show(subst_btn);
713         gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0);
714         g_signal_connect(G_OBJECT(subst_btn), "clicked",
715                          G_CALLBACK(prefs_matcher_substitute_cb),
716                          NULL);
717
718         del_btn = gtk_button_new_from_stock(GTK_STOCK_DELETE);
719         gtk_widget_show(del_btn);
720         gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0);
721         g_signal_connect(G_OBJECT(del_btn), "clicked",
722                          G_CALLBACK(prefs_matcher_delete_cb), NULL);
723
724         /* boolean operation */
725
726         bool_op_label = gtk_label_new(_("Boolean Op"));
727         gtk_misc_set_alignment(GTK_MISC(value_label), 0, 0.5);
728         gtk_widget_show(bool_op_label);
729         gtk_box_pack_start(GTK_BOX(btn_hbox), bool_op_label,
730                            FALSE, FALSE, 0);
731
732         bool_op_combo = gtk_combo_new();
733         gtk_widget_show(bool_op_combo);
734         gtk_widget_set_size_request(bool_op_combo, 60, -1);
735         bool_op_list = GTK_COMBO(bool_op_combo)->list;
736         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(bool_op_combo)->entry),
737                                FALSE);
738
739         combo_items = NULL;
740
741         for (i = 0; i < (gint) (sizeof(bool_op_text) / sizeof(gchar *)); i++) {
742                 combo_items = g_list_append(combo_items,
743                                             (gpointer) _(bool_op_text[i]));
744         }
745         gtk_combo_set_popdown_strings(GTK_COMBO(bool_op_combo), combo_items);
746
747         g_list_free(combo_items);
748
749         gtk_box_pack_start(GTK_BOX(btn_hbox), bool_op_combo,
750                            FALSE, FALSE, 0);
751
752         cond_hbox = gtk_hbox_new(FALSE, 8);
753         gtk_widget_show(cond_hbox);
754         gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0);
755
756         cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
757         gtk_widget_show(cond_scrolledwin);
758         gtk_widget_set_size_request(cond_scrolledwin, -1, 150);
759         gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin,
760                            TRUE, TRUE, 0);
761         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cond_scrolledwin),
762                                        GTK_POLICY_AUTOMATIC,
763                                        GTK_POLICY_AUTOMATIC);
764
765         cond_list_view = prefs_matcher_list_view_create();                                     
766         gtk_widget_show(cond_list_view);
767         gtk_container_add(GTK_CONTAINER(cond_scrolledwin), cond_list_view);
768
769         btn_vbox = gtk_vbox_new(FALSE, 8);
770         gtk_widget_show(btn_vbox);
771         gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0);
772
773         up_btn = gtk_button_new_from_stock(GTK_STOCK_GO_UP);
774         gtk_widget_show(up_btn);
775         gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0);
776         g_signal_connect(G_OBJECT(up_btn), "clicked",
777                          G_CALLBACK(prefs_matcher_up), NULL);
778
779         down_btn = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN);
780         gtk_widget_show(down_btn);
781         gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0);
782         g_signal_connect(G_OBJECT(down_btn), "clicked",
783                          G_CALLBACK(prefs_matcher_down), NULL);
784
785         if (!geometry.min_height) {
786                 geometry.min_width = 520;
787                 geometry.min_height = 368;
788         }
789
790         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
791                                       GDK_HINT_MIN_SIZE);
792         gtk_widget_set_size_request(window, prefs_common.matcherwin_width,
793                                     prefs_common.matcherwin_height);
794
795         gtk_widget_show_all(window);
796         gtk_widget_hide(header_addr_label);
797         gtk_widget_hide(header_addr_combo);
798         gtk_widget_hide(addressbook_select_btn);
799         gtk_widget_hide(addressbook_folder_label);
800
801         matcher.window    = window;
802
803         matcher.ok_btn = ok_btn;
804
805         matcher.criteria_list = criteria_list;
806         matcher.header_combo = header_combo;
807         matcher.header_entry = header_entry;
808         matcher.header_label = header_label;
809         matcher.header_addr_combo = header_addr_combo;
810         matcher.header_addr_entry = header_addr_entry;
811         matcher.header_addr_label = header_addr_label;
812         matcher.value_entry = value_entry;
813         matcher.value_label = value_label;
814         matcher.addressbook_folder_label = addressbook_folder_label;
815         matcher.addressbook_folder_combo = addressbook_folder_combo;
816         matcher.predicate_label = predicate_label;
817         matcher.predicate_list = predicate_list;
818         matcher.predicate_combo = predicate_combo;
819         matcher.predicate_flag_list = predicate_flag_list;
820         matcher.predicate_flag_combo = predicate_flag_combo;
821         matcher.case_checkbtn = case_checkbtn;
822         matcher.regexp_checkbtn = regexp_checkbtn;
823         matcher.bool_op_list = bool_op_list;
824         matcher.test_btn = test_btn;
825         matcher.addressbook_select_btn = addressbook_select_btn;
826         matcher.color_optmenu = color_optmenu;
827         matcher.criteria_table = criteria_table;
828
829         matcher.cond_list_view = cond_list_view;
830
831         matcher.selected_criteria = -1;
832         prefs_matcher_criteria_select(GTK_LIST(criteria_list), NULL, NULL);
833 }
834
835 /*!
836  *\brief        Set the contents of a row
837  *
838  *\param        row Index of row to set
839  *\param        prop Condition to set
840  *
841  *\return       gint Row index \a prop has been added
842  */
843 static void prefs_matcher_list_view_set_row(GtkTreeIter *row, MatcherProp *prop)
844 {
845         gchar *matcher_str;
846
847         if (prop == NULL) {
848                 prefs_matcher_list_view_insert_matcher(matcher.cond_list_view,
849                                                        NULL, _("(New)"), FALSE);
850                 return;                                                
851         }
852
853         matcher_str = matcherprop_to_string(prop);
854         if (!row)
855                 prefs_matcher_list_view_insert_matcher(matcher.cond_list_view,
856                                                        NULL, matcher_str,
857                                                        TRUE);
858         else
859                 prefs_matcher_list_view_insert_matcher(matcher.cond_list_view,
860                                                        row, matcher_str, 
861                                                        TRUE);
862         g_free(matcher_str);
863 }
864
865 /*!
866  *\brief        Clears a condition in the list widget
867  */
868 static void prefs_matcher_reset_condition(void)
869 {
870         gtk_list_select_item(GTK_LIST(matcher.criteria_list), 0);
871         gtk_list_select_item(GTK_LIST(matcher.predicate_list), 0);
872         gtk_entry_set_text(GTK_ENTRY(matcher.header_entry), "");
873         gtk_entry_set_text(GTK_ENTRY(matcher.header_addr_entry), "");
874         gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), "");
875         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(matcher.addressbook_folder_combo)->entry), "");
876 }
877
878 /*!
879  *\brief        Initializes dialog with a set of conditions
880  *
881  *\param        matchers List of conditions
882  */
883 static void prefs_matcher_set_dialog(MatcherList *matchers)
884 {
885         GSList *cur;
886         gboolean bool_op = 1;
887         GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model
888                                 (GTK_TREE_VIEW(matcher.cond_list_view)));
889
890         gtk_list_store_clear(store);                            
891
892         prefs_matcher_list_view_set_row(NULL, NULL);
893         if (matchers != NULL) {
894                 for (cur = matchers->matchers; cur != NULL;
895                      cur = g_slist_next(cur)) {
896                         MatcherProp *prop;
897                         prop = (MatcherProp *) cur->data;
898                         prefs_matcher_list_view_set_row(NULL, prop);
899                 }
900
901                 bool_op = matchers->bool_and;
902         }
903         
904         gtk_list_select_item(GTK_LIST(matcher.bool_op_list), bool_op);
905
906         prefs_matcher_reset_condition();
907 }
908
909 /*!
910  *\brief        Converts current conditions in list box in
911  *              a matcher list used by the matcher.
912  *
913  *\return       MatcherList * List of conditions.
914  */
915 static MatcherList *prefs_matcher_get_list(void)
916 {
917         gchar *matcher_str;
918         MatcherProp *prop;
919         gboolean bool_and;
920         GSList *matcher_list;
921         MatcherList *matchers;
922         GtkTreeModel *model;
923         GtkTreeIter iter;
924
925         model = gtk_tree_view_get_model(GTK_TREE_VIEW(matcher.cond_list_view));
926         if (!gtk_tree_model_get_iter_first(model, &iter))
927                 return NULL;
928
929         matcher_list = NULL;
930
931         do {
932                 gboolean is_valid;
933         
934                 gtk_tree_model_get(model, &iter,
935                                    PREFS_MATCHER_COND, &matcher_str,
936                                    PREFS_MATCHER_COND_VALID, &is_valid,
937                                    -1);
938                 
939                 if (is_valid) {
940                         /* tmp = matcher_str; */
941                         prop = matcher_parser_get_prop(matcher_str);
942                         g_free(matcher_str);
943                         if (prop == NULL)
944                                 break;
945                         
946                         matcher_list = g_slist_append(matcher_list, prop);
947                 }
948         } while (gtk_tree_model_iter_next(model, &iter));
949
950         bool_and = get_sel_from_list(GTK_LIST(matcher.bool_op_list));
951
952         matchers = matcherlist_new(matcher_list, bool_and);
953
954         return matchers;
955 }
956
957 /*!
958  *\brief        Maps a keyword id (see #get_matchparser_tab_id) to a 
959  *              criteria type (see first parameter of #matcherprop_new
960  *              or #matcherprop_unquote_new)
961  *
962  *\param        matching_id Id returned by the matcher parser.
963  *
964  *\return       gint One of the CRITERIA_xxx constants.
965  */
966 static gint prefs_matcher_get_criteria_from_matching(gint matching_id)
967 {
968         switch(matching_id) {
969         case MATCHCRITERIA_ALL:
970                 return CRITERIA_ALL;
971         case MATCHCRITERIA_NOT_UNREAD:
972         case MATCHCRITERIA_UNREAD:
973                 return CRITERIA_UNREAD;
974         case MATCHCRITERIA_NOT_NEW:
975         case MATCHCRITERIA_NEW:
976                 return CRITERIA_NEW;
977         case MATCHCRITERIA_NOT_MARKED:
978         case MATCHCRITERIA_MARKED:
979                 return CRITERIA_MARKED;
980         case MATCHCRITERIA_NOT_DELETED:
981         case MATCHCRITERIA_DELETED:
982                 return CRITERIA_DELETED;
983         case MATCHCRITERIA_NOT_REPLIED:
984         case MATCHCRITERIA_REPLIED:
985                 return CRITERIA_REPLIED;
986         case MATCHCRITERIA_NOT_FORWARDED:
987         case MATCHCRITERIA_FORWARDED:
988                 return CRITERIA_FORWARDED;
989         case MATCHCRITERIA_LOCKED:
990         case MATCHCRITERIA_NOT_LOCKED:
991                 return CRITERIA_LOCKED;
992         case MATCHCRITERIA_NOT_SPAM:
993         case MATCHCRITERIA_SPAM:
994                 return CRITERIA_SPAM;
995         case MATCHCRITERIA_PARTIAL:
996         case MATCHCRITERIA_NOT_PARTIAL:
997                 return CRITERIA_PARTIAL;
998         case MATCHCRITERIA_COLORLABEL:
999         case MATCHCRITERIA_NOT_COLORLABEL:
1000                 return CRITERIA_COLORLABEL;
1001         case MATCHCRITERIA_IGNORE_THREAD:
1002         case MATCHCRITERIA_NOT_IGNORE_THREAD:
1003                 return CRITERIA_IGNORE_THREAD;
1004         case MATCHCRITERIA_NOT_SUBJECT:
1005         case MATCHCRITERIA_SUBJECT:
1006                 return CRITERIA_SUBJECT;
1007         case MATCHCRITERIA_NOT_FROM:
1008         case MATCHCRITERIA_FROM:
1009                 return CRITERIA_FROM;
1010         case MATCHCRITERIA_NOT_TO:
1011         case MATCHCRITERIA_TO:
1012                 return CRITERIA_TO;
1013         case MATCHCRITERIA_NOT_CC:
1014         case MATCHCRITERIA_CC:
1015                 return CRITERIA_CC;
1016         case MATCHCRITERIA_NOT_NEWSGROUPS:
1017         case MATCHCRITERIA_NEWSGROUPS:
1018                 return CRITERIA_NEWSGROUPS;
1019         case MATCHCRITERIA_NOT_INREPLYTO:
1020         case MATCHCRITERIA_INREPLYTO:
1021                 return CRITERIA_INREPLYTO;
1022         case MATCHCRITERIA_NOT_REFERENCES:
1023         case MATCHCRITERIA_REFERENCES:
1024                 return CRITERIA_REFERENCES;
1025         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1026         case MATCHCRITERIA_TO_OR_CC:
1027                 return CRITERIA_TO_OR_CC;
1028         case MATCHCRITERIA_NOT_TAG:
1029         case MATCHCRITERIA_TAG:
1030                 return CRITERIA_TAG;
1031         case MATCHCRITERIA_NOT_TAGGED:
1032         case MATCHCRITERIA_TAGGED:
1033                 return CRITERIA_TAGGED;
1034         case MATCHCRITERIA_NOT_BODY_PART:
1035         case MATCHCRITERIA_BODY_PART:
1036                 return CRITERIA_BODY_PART;
1037         case MATCHCRITERIA_NOT_MESSAGE:
1038         case MATCHCRITERIA_MESSAGE:
1039                 return CRITERIA_MESSAGE;
1040         case MATCHCRITERIA_NOT_HEADERS_PART:
1041         case MATCHCRITERIA_HEADERS_PART:
1042                 return CRITERIA_HEADERS_PART;
1043         case MATCHCRITERIA_NOT_HEADER:
1044         case MATCHCRITERIA_HEADER:
1045                 return CRITERIA_HEADER;
1046         case MATCHCRITERIA_AGE_GREATER:
1047                 return CRITERIA_AGE_GREATER;
1048         case MATCHCRITERIA_AGE_LOWER:
1049                 return CRITERIA_AGE_LOWER;
1050         case MATCHCRITERIA_SCORE_GREATER:
1051                 return CRITERIA_SCORE_GREATER;
1052         case MATCHCRITERIA_SCORE_LOWER:
1053                 return CRITERIA_SCORE_LOWER;
1054         case MATCHCRITERIA_SCORE_EQUAL:
1055                 return CRITERIA_SCORE_EQUAL;
1056         case MATCHCRITERIA_NOT_TEST:
1057         case MATCHCRITERIA_TEST:
1058                 return CRITERIA_TEST;
1059         case MATCHCRITERIA_SIZE_GREATER:
1060                 return CRITERIA_SIZE_GREATER;
1061         case MATCHCRITERIA_SIZE_SMALLER:
1062                 return CRITERIA_SIZE_SMALLER;
1063         case MATCHCRITERIA_SIZE_EQUAL:
1064                 return CRITERIA_SIZE_EQUAL;
1065         case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1066         case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1067                 return CRITERIA_FOUND_IN_ADDRESSBOOK;
1068         default:
1069                 return -1;
1070         }
1071 }
1072
1073 /*!
1074  *\brief        Returns the matcher keyword id from a criteria id
1075  *
1076  *\param        criteria_id Criteria id (should not be the negate
1077  *              one)
1078  *
1079  *\return       gint A matcher keyword id. See #get_matchparser_tab_id.
1080  */
1081 static gint prefs_matcher_get_matching_from_criteria(gint criteria_id)
1082 {
1083         switch (criteria_id) {
1084         case CRITERIA_ALL:
1085                 return MATCHCRITERIA_ALL;
1086         case CRITERIA_UNREAD:
1087                 return MATCHCRITERIA_UNREAD;
1088         case CRITERIA_NEW:
1089                 return MATCHCRITERIA_NEW;
1090         case CRITERIA_MARKED:
1091                 return MATCHCRITERIA_MARKED;
1092         case CRITERIA_DELETED:
1093                 return MATCHCRITERIA_DELETED;
1094         case CRITERIA_REPLIED:
1095                 return MATCHCRITERIA_REPLIED;
1096         case CRITERIA_FORWARDED:
1097                 return MATCHCRITERIA_FORWARDED;
1098         case CRITERIA_LOCKED:
1099                 return MATCHCRITERIA_LOCKED;
1100         case CRITERIA_SPAM:
1101                 return MATCHCRITERIA_SPAM;
1102         case CRITERIA_PARTIAL:
1103                 return MATCHCRITERIA_PARTIAL;
1104         case CRITERIA_COLORLABEL:
1105                 return MATCHCRITERIA_COLORLABEL;
1106         case CRITERIA_IGNORE_THREAD:
1107                 return MATCHCRITERIA_IGNORE_THREAD;
1108         case CRITERIA_SUBJECT:
1109                 return MATCHCRITERIA_SUBJECT;
1110         case CRITERIA_FROM:
1111                 return MATCHCRITERIA_FROM;
1112         case CRITERIA_TO:
1113                 return MATCHCRITERIA_TO;
1114         case CRITERIA_CC:
1115                 return MATCHCRITERIA_CC;
1116         case CRITERIA_TO_OR_CC:
1117                 return MATCHCRITERIA_TO_OR_CC;
1118         case CRITERIA_TAG:
1119                 return MATCHCRITERIA_TAG;
1120         case CRITERIA_TAGGED:
1121                 return MATCHCRITERIA_TAGGED;
1122         case CRITERIA_NEWSGROUPS:
1123                 return MATCHCRITERIA_NEWSGROUPS;
1124         case CRITERIA_INREPLYTO:
1125                 return MATCHCRITERIA_INREPLYTO;
1126         case CRITERIA_REFERENCES:
1127                 return MATCHCRITERIA_REFERENCES;
1128         case CRITERIA_AGE_GREATER:
1129                 return MATCHCRITERIA_AGE_GREATER;
1130         case CRITERIA_AGE_LOWER:
1131                 return MATCHCRITERIA_AGE_LOWER;
1132         case CRITERIA_SCORE_GREATER:
1133                 return MATCHCRITERIA_SCORE_GREATER;
1134         case CRITERIA_SCORE_LOWER:
1135                 return MATCHCRITERIA_SCORE_LOWER;
1136         case CRITERIA_SCORE_EQUAL:
1137                 return MATCHCRITERIA_SCORE_EQUAL;
1138         case CRITERIA_HEADER:
1139                 return MATCHCRITERIA_HEADER;
1140         case CRITERIA_HEADERS_PART:
1141                 return MATCHCRITERIA_HEADERS_PART;
1142         case CRITERIA_BODY_PART:
1143                 return MATCHCRITERIA_BODY_PART;
1144         case CRITERIA_MESSAGE:
1145                 return MATCHCRITERIA_MESSAGE;
1146         case CRITERIA_TEST:
1147                 return MATCHCRITERIA_TEST;
1148         case CRITERIA_SIZE_GREATER:
1149                 return MATCHCRITERIA_SIZE_GREATER;
1150         case CRITERIA_SIZE_SMALLER:
1151                 return MATCHCRITERIA_SIZE_SMALLER;
1152         case CRITERIA_SIZE_EQUAL:
1153                 return MATCHCRITERIA_SIZE_EQUAL;
1154         case CRITERIA_FOUND_IN_ADDRESSBOOK:
1155                 return MATCHCRITERIA_FOUND_IN_ADDRESSBOOK;
1156         default:
1157                 return -1;
1158         }
1159 }
1160
1161 /*!
1162  *\brief        Returns the negate matcher keyword id from a matcher keyword
1163  *              id.
1164  *
1165  *\param        matcher_criteria Matcher keyword id. 
1166  *
1167  *\return       gint A matcher keyword id. See #get_matchparser_tab_id.
1168  */
1169 static gint prefs_matcher_not_criteria(gint matcher_criteria)
1170 {
1171         switch(matcher_criteria) {
1172         case MATCHCRITERIA_UNREAD:
1173                 return MATCHCRITERIA_NOT_UNREAD;
1174         case MATCHCRITERIA_NEW:
1175                 return MATCHCRITERIA_NOT_NEW;
1176         case MATCHCRITERIA_MARKED:
1177                 return MATCHCRITERIA_NOT_MARKED;
1178         case MATCHCRITERIA_DELETED:
1179                 return MATCHCRITERIA_NOT_DELETED;
1180         case MATCHCRITERIA_REPLIED:
1181                 return MATCHCRITERIA_NOT_REPLIED;
1182         case MATCHCRITERIA_FORWARDED:
1183                 return MATCHCRITERIA_NOT_FORWARDED;
1184         case MATCHCRITERIA_LOCKED:
1185                 return MATCHCRITERIA_NOT_LOCKED;
1186         case MATCHCRITERIA_SPAM:
1187                 return MATCHCRITERIA_NOT_SPAM;
1188         case MATCHCRITERIA_PARTIAL:
1189                 return MATCHCRITERIA_NOT_PARTIAL;
1190         case MATCHCRITERIA_COLORLABEL:
1191                 return MATCHCRITERIA_NOT_COLORLABEL;
1192         case MATCHCRITERIA_IGNORE_THREAD:
1193                 return MATCHCRITERIA_NOT_IGNORE_THREAD;
1194         case MATCHCRITERIA_SUBJECT:
1195                 return MATCHCRITERIA_NOT_SUBJECT;
1196         case MATCHCRITERIA_FROM:
1197                 return MATCHCRITERIA_NOT_FROM;
1198         case MATCHCRITERIA_TO:
1199                 return MATCHCRITERIA_NOT_TO;
1200         case MATCHCRITERIA_CC:
1201                 return MATCHCRITERIA_NOT_CC;
1202         case MATCHCRITERIA_TO_OR_CC:
1203                 return MATCHCRITERIA_NOT_TO_AND_NOT_CC;
1204         case MATCHCRITERIA_TAG:
1205                 return MATCHCRITERIA_NOT_TAG;
1206         case MATCHCRITERIA_TAGGED:
1207                 return MATCHCRITERIA_NOT_TAGGED;
1208         case MATCHCRITERIA_NEWSGROUPS:
1209                 return MATCHCRITERIA_NOT_NEWSGROUPS;
1210         case MATCHCRITERIA_INREPLYTO:
1211                 return MATCHCRITERIA_NOT_INREPLYTO;
1212         case MATCHCRITERIA_REFERENCES:
1213                 return MATCHCRITERIA_NOT_REFERENCES;
1214         case MATCHCRITERIA_HEADER:
1215                 return MATCHCRITERIA_NOT_HEADER;
1216         case MATCHCRITERIA_HEADERS_PART:
1217                 return MATCHCRITERIA_NOT_HEADERS_PART;
1218         case MATCHCRITERIA_MESSAGE:
1219                 return MATCHCRITERIA_NOT_MESSAGE;
1220         case MATCHCRITERIA_TEST:
1221                 return MATCHCRITERIA_NOT_TEST;
1222         case MATCHCRITERIA_BODY_PART:
1223                 return MATCHCRITERIA_NOT_BODY_PART;
1224         case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1225                 return MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK;
1226         default:
1227                 return matcher_criteria;
1228         }
1229 }
1230
1231 /*!
1232  *\brief        Converts the text in the selected row to a 
1233  *              matcher structure
1234  *
1235  *\return       MatcherProp * Newly allocated matcher structure.
1236  */
1237 static MatcherProp *prefs_matcher_dialog_to_matcher(void)
1238 {
1239         MatcherProp *matcherprop;
1240         gint criteria;
1241         gint matchtype;
1242         gint value_pred;
1243         gint value_pred_flag;
1244         gint value_criteria;
1245         gboolean use_regexp;
1246         gboolean case_sensitive;
1247         const gchar *header;
1248         const gchar *expr;
1249         gint value;
1250         const gchar *value_str;
1251
1252         value_criteria = get_sel_from_list(GTK_LIST(matcher.criteria_list));
1253
1254         criteria = prefs_matcher_get_matching_from_criteria(value_criteria);
1255
1256         value_pred = get_sel_from_list(GTK_LIST(matcher.predicate_list));
1257         value_pred_flag = get_sel_from_list(GTK_LIST(matcher.predicate_flag_list));
1258
1259         use_regexp = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(matcher.regexp_checkbtn));
1260         case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(matcher.case_checkbtn));
1261
1262         switch (value_criteria) {
1263         case CRITERIA_UNREAD:
1264         case CRITERIA_NEW:
1265         case CRITERIA_MARKED:
1266         case CRITERIA_DELETED:
1267         case CRITERIA_REPLIED:
1268         case CRITERIA_FORWARDED:
1269         case CRITERIA_LOCKED:
1270         case CRITERIA_SPAM:
1271         case CRITERIA_PARTIAL:
1272         case CRITERIA_TEST:
1273         case CRITERIA_COLORLABEL:
1274         case CRITERIA_IGNORE_THREAD:
1275         case CRITERIA_FOUND_IN_ADDRESSBOOK:
1276         case CRITERIA_TAGGED:
1277                 if (value_pred_flag == PREDICATE_FLAG_DISABLED)
1278                         criteria = prefs_matcher_not_criteria(criteria);
1279                 break;
1280         case CRITERIA_SUBJECT:
1281         case CRITERIA_FROM:
1282         case CRITERIA_TO:
1283         case CRITERIA_CC:
1284         case CRITERIA_TO_OR_CC:
1285         case CRITERIA_TAG:
1286         case CRITERIA_NEWSGROUPS:
1287         case CRITERIA_INREPLYTO:
1288         case CRITERIA_REFERENCES:
1289         case CRITERIA_HEADERS_PART:
1290         case CRITERIA_BODY_PART:
1291         case CRITERIA_MESSAGE:
1292         case CRITERIA_AGE_GREATER:
1293         case CRITERIA_AGE_LOWER:
1294         case CRITERIA_HEADER:
1295                 if (value_pred == PREDICATE_DOES_NOT_CONTAIN)
1296                         criteria = prefs_matcher_not_criteria(criteria);
1297                 break;
1298         }
1299
1300         if (use_regexp) {
1301                 if (case_sensitive)
1302                         matchtype = MATCHTYPE_REGEXP;
1303                 else
1304                         matchtype = MATCHTYPE_REGEXPCASE;
1305         }
1306         else {
1307                 if (case_sensitive)
1308                         matchtype = MATCHTYPE_MATCH;
1309                 else
1310                         matchtype = MATCHTYPE_MATCHCASE;
1311         }
1312
1313         header = NULL;
1314         expr = NULL;
1315         value = 0;
1316
1317         switch (value_criteria) {
1318         case CRITERIA_ALL:
1319         case CRITERIA_UNREAD:
1320         case CRITERIA_NEW:
1321         case CRITERIA_MARKED:
1322         case CRITERIA_DELETED:
1323         case CRITERIA_REPLIED:
1324         case CRITERIA_FORWARDED:
1325         case CRITERIA_LOCKED:
1326         case CRITERIA_SPAM:
1327         case CRITERIA_PARTIAL:
1328         case CRITERIA_IGNORE_THREAD:
1329         case CRITERIA_TAGGED:
1330                 break;
1331
1332         case CRITERIA_SUBJECT:
1333         case CRITERIA_FROM:
1334         case CRITERIA_TO:
1335         case CRITERIA_CC:
1336         case CRITERIA_TO_OR_CC:
1337         case CRITERIA_TAG:
1338         case CRITERIA_NEWSGROUPS:
1339         case CRITERIA_INREPLYTO:
1340         case CRITERIA_REFERENCES:
1341         case CRITERIA_HEADERS_PART:
1342         case CRITERIA_BODY_PART:
1343         case CRITERIA_MESSAGE:
1344         case CRITERIA_TEST:
1345                 expr = gtk_entry_get_text(GTK_ENTRY(matcher.value_entry));
1346                 break;
1347
1348         case CRITERIA_AGE_GREATER:
1349         case CRITERIA_AGE_LOWER:
1350         case CRITERIA_SCORE_GREATER:
1351         case CRITERIA_SCORE_LOWER:
1352         case CRITERIA_SCORE_EQUAL:
1353         case CRITERIA_SIZE_GREATER:
1354         case CRITERIA_SIZE_SMALLER:
1355         case CRITERIA_SIZE_EQUAL:
1356                 value_str = gtk_entry_get_text(GTK_ENTRY(matcher.value_entry));
1357
1358                 if (*value_str == '\0') {
1359                     alertpanel_error(_("Value is not set."));
1360                     return NULL;
1361                 }
1362
1363                 value = atoi(value_str);
1364                 break;
1365                 
1366         case CRITERIA_COLORLABEL:
1367                 value = colorlabel_get_color_menu_active_item
1368                         (gtk_option_menu_get_menu(GTK_OPTION_MENU
1369                                 (matcher.color_optmenu))); 
1370                 break;
1371
1372         case CRITERIA_HEADER:
1373                 header = gtk_entry_get_text(GTK_ENTRY(matcher.header_entry));
1374                 expr = gtk_entry_get_text(GTK_ENTRY(matcher.value_entry));
1375
1376                 if (*header == '\0') {
1377                     alertpanel_error(_("Header name is not set."));
1378                     return NULL;
1379                 }
1380                 break;
1381
1382         case CRITERIA_FOUND_IN_ADDRESSBOOK:
1383                 header = gtk_entry_get_text(GTK_ENTRY(matcher.header_addr_entry));
1384                 expr = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(matcher.addressbook_folder_combo)->entry));
1385
1386                 if (*header == '\0') {
1387                     alertpanel_error(_("Header name is not set."));
1388                     return NULL;
1389                 }
1390                 if (*expr == '\0') {
1391                         gchar *msg;
1392                         gchar *tmp;
1393
1394                         if (strcasecmp(header, Q_("Filtering Matcher Menu|All")) == 0)
1395                                 tmp = g_strdup(_("all addresses in all headers"));
1396                         else
1397                         if (strcasecmp(header, _("Any")) == 0)
1398                                 tmp = g_strdup(_("any address in any header"));
1399                         else
1400                                 tmp = g_strdup_printf(_("the address(es) in header '%s'"), header);
1401                         msg = g_strdup_printf(_("Book/folder path is not set.\n\n"
1402                                                 "If you want to match %s against the whole address book, "
1403                                                 "you have to select 'Any' from the book/folder drop-down list."),
1404                                                 tmp);
1405                     alertpanel_error(msg);
1406                         g_free(msg);
1407                         g_free(tmp);
1408                     return NULL;
1409                 }
1410                 /* don't store translated "Any"/"All" in matcher expressions */
1411                 if (strcasecmp(header, Q_("Filtering Matcher Menu|All")) == 0)
1412                         header = "All";
1413                 else
1414                         if (strcasecmp(header, _("Any")) == 0)
1415                                 header = "Any";
1416                 if (strcasecmp(expr, _("Any")) == 0)
1417                         expr = "Any";
1418                 break;
1419         }
1420
1421         matcherprop = matcherprop_new(criteria, header, matchtype,
1422                                       expr, value);
1423
1424         return matcherprop;
1425 }
1426
1427 /*!
1428  *\brief        Signal handler for register button
1429  */
1430 static void prefs_matcher_register_cb(void)
1431 {
1432         MatcherProp *matcherprop;
1433         
1434         matcherprop = prefs_matcher_dialog_to_matcher();
1435         if (matcherprop == NULL)
1436                 return;
1437
1438         prefs_matcher_list_view_set_row(NULL, matcherprop);
1439
1440         matcherprop_free(matcherprop);
1441
1442         prefs_matcher_reset_condition();
1443 }
1444
1445 /*!
1446  *\brief        Signal handler for substitute button
1447  */
1448 static void prefs_matcher_substitute_cb(void)
1449 {
1450         MatcherProp *matcherprop;
1451         GtkTreeIter row;
1452         GtkTreeSelection *selection;
1453         GtkTreeModel *model;
1454         gboolean is_valid;
1455
1456         selection = gtk_tree_view_get_selection
1457                         (GTK_TREE_VIEW(matcher.cond_list_view));
1458         
1459         if (!gtk_tree_selection_get_selected(selection, &model, &row))
1460                 return;
1461         
1462         gtk_tree_model_get(model, &row, 
1463                            PREFS_MATCHER_COND_VALID, &is_valid,
1464                            -1);
1465         if (!is_valid)
1466                 return;
1467
1468         matcherprop = prefs_matcher_dialog_to_matcher();
1469         if (matcherprop == NULL)
1470                 return;
1471
1472         prefs_matcher_list_view_set_row(&row, matcherprop);
1473
1474         matcherprop_free(matcherprop);
1475
1476         prefs_matcher_reset_condition();
1477 }
1478
1479 /*!
1480  *\brief        Signal handler for delete button
1481  */
1482 static void prefs_matcher_delete_cb(void)
1483 {
1484         GtkTreeIter row;
1485         GtkTreeSelection *selection;
1486         GtkTreeModel *model;
1487         gboolean is_valid;
1488
1489         selection = gtk_tree_view_get_selection
1490                         (GTK_TREE_VIEW(matcher.cond_list_view));
1491         
1492         if (!gtk_tree_selection_get_selected(selection, &model, &row))
1493                 return;
1494                 
1495         gtk_tree_model_get(model, &row, 
1496                            PREFS_MATCHER_COND_VALID, &is_valid,
1497                            -1);
1498
1499         if (!is_valid)
1500                 return;
1501
1502         gtk_list_store_remove(GTK_LIST_STORE(model), &row);             
1503
1504         prefs_matcher_reset_condition();
1505 }
1506
1507 /*!
1508  *\brief        Signal handler for 'move up' button
1509  */
1510 static void prefs_matcher_up(void)
1511 {
1512         GtkTreePath *prev, *sel, *try;
1513         GtkTreeIter isel;
1514         GtkListStore *store = NULL;
1515         GtkTreeModel *model = NULL;
1516         GtkTreeIter iprev;
1517         
1518         if (!gtk_tree_selection_get_selected
1519                 (gtk_tree_view_get_selection
1520                         (GTK_TREE_VIEW(matcher.cond_list_view)),
1521                  &model,        
1522                  &isel))
1523                 return;
1524         store = (GtkListStore *)model;
1525         sel = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &isel);
1526         if (!sel)
1527                 return;
1528         
1529         /* no move if we're at row 0 or 1, looks phony, but other
1530          * solutions are more convoluted... */
1531         try = gtk_tree_path_copy(sel);
1532         if (!gtk_tree_path_prev(try) || !gtk_tree_path_prev(try)) {
1533                 gtk_tree_path_free(try);
1534                 gtk_tree_path_free(sel);
1535                 return;
1536         }
1537         gtk_tree_path_free(try);
1538
1539         prev = gtk_tree_path_copy(sel);         
1540         if (gtk_tree_path_prev(prev)) {
1541                 gtk_tree_model_get_iter(GTK_TREE_MODEL(store),
1542                                         &iprev, prev);
1543                 gtk_list_store_swap(store, &iprev, &isel);
1544                 /* XXX: GTK2 select row?? */
1545         }
1546
1547         gtk_tree_path_free(sel);
1548         gtk_tree_path_free(prev);
1549 }
1550
1551 /*!
1552  *\brief        Signal handler for 'move down' button
1553  */
1554 static void prefs_matcher_down(void)
1555 {
1556         GtkListStore *store = NULL;
1557         GtkTreeModel *model = NULL;
1558         GtkTreeIter next, sel;
1559         GtkTreePath *try;
1560         
1561         if (!gtk_tree_selection_get_selected
1562                 (gtk_tree_view_get_selection
1563                         (GTK_TREE_VIEW(matcher.cond_list_view)),
1564                  &model,
1565                  &sel))
1566                 return;
1567         store = (GtkListStore *)model;
1568         try = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &sel);
1569         if (!try) 
1570                 return;
1571         
1572         /* move when not at row 0 ... */
1573         if (gtk_tree_path_prev(try)) {
1574                 next = sel;
1575                 if (gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &next))
1576                         gtk_list_store_swap(store, &next, &sel);
1577         }
1578                 
1579         gtk_tree_path_free(try);
1580 }
1581
1582 /*!
1583  *\brief        Helper function that allows us to replace the 'Value' entry box
1584  *              by another widget.
1585  *
1586  *\param        old_widget Widget that needs to be removed.
1587  *\param        new_widget Replacement widget
1588  */
1589 static void prefs_matcher_set_value_widget(GtkWidget *old_widget, 
1590                                            GtkWidget *new_widget)
1591 {
1592         /* TODO: find out why the following spews harmless "parent errors" */
1593
1594         /* NOTE: we first need to bump up the refcount of the old_widget,
1595          * because the gtkut_container_remove() will otherwise destroy it */
1596         gtk_widget_ref(old_widget);
1597         gtkut_container_remove(GTK_CONTAINER(matcher.criteria_table), old_widget);
1598         gtk_widget_show(new_widget);
1599         gtk_widget_set_size_request(new_widget, 200, -1);
1600         gtk_table_attach(GTK_TABLE(matcher.criteria_table), new_widget, 
1601                          2, 3, 1, 2, 
1602                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 
1603                          0, 0, 0);
1604 }
1605
1606 static void prefs_matcher_disable_widget(GtkWidget* widget)
1607 {
1608         g_return_if_fail( widget != NULL);
1609
1610         gtk_widget_set_sensitive(widget, FALSE);
1611         gtk_widget_hide(widget);
1612 }
1613
1614 static void prefs_matcher_enable_widget(GtkWidget* widget)
1615 {
1616         g_return_if_fail( widget != NULL);
1617
1618         gtk_widget_set_sensitive(widget, TRUE);
1619         gtk_widget_show(widget);
1620 }
1621
1622 /*!
1623  *\brief        Change widgets depending on the selected condition
1624  *
1625  *\param        list List widget
1626  *\param        widget Not used
1627  *\param        user_data Not used      
1628  */
1629 static void prefs_matcher_criteria_select(GtkList *list,
1630                                           GtkWidget *widget,
1631                                           gpointer user_data)
1632 {
1633         gint value, old_value;
1634
1635         old_value = matcher.selected_criteria;
1636         matcher.selected_criteria = value = get_sel_from_list
1637                 (GTK_LIST(matcher.criteria_list));
1638
1639         if (old_value == matcher.selected_criteria)
1640                 return;
1641
1642         /* CLAWS: the value widget is currently either the color label combo box,
1643          * or a GtkEntry, so kiss for now */
1644         if (matcher.selected_criteria == CRITERIA_COLORLABEL) { 
1645                 prefs_matcher_set_value_widget(matcher.value_entry, 
1646                                                matcher.color_optmenu);
1647         } else if (old_value == CRITERIA_COLORLABEL) {
1648                 prefs_matcher_set_value_widget(matcher.color_optmenu,
1649                                                matcher.value_entry);
1650         }                                              
1651
1652         switch (value) {
1653         case CRITERIA_ALL:
1654                 prefs_matcher_disable_widget(matcher.header_combo);
1655                 prefs_matcher_disable_widget(matcher.header_label);
1656                 prefs_matcher_disable_widget(matcher.header_addr_combo);
1657                 prefs_matcher_disable_widget(matcher.header_addr_label);
1658                 prefs_matcher_disable_widget(matcher.value_label);
1659                 prefs_matcher_disable_widget(matcher.value_entry);
1660                 prefs_matcher_disable_widget(matcher.addressbook_folder_label);
1661                 prefs_matcher_disable_widget(matcher.addressbook_folder_combo);
1662                 prefs_matcher_disable_widget(matcher.predicate_label);
1663                 prefs_matcher_disable_widget(matcher.predicate_combo);
1664                 prefs_matcher_disable_widget(matcher.predicate_flag_combo);
1665                 prefs_matcher_disable_widget(matcher.case_checkbtn);
1666                 prefs_matcher_disable_widget(matcher.regexp_checkbtn);
1667                 prefs_matcher_disable_widget(matcher.test_btn);
1668                 prefs_matcher_disable_widget(matcher.addressbook_select_btn);
1669                 break;
1670
1671         case CRITERIA_UNREAD:
1672         case CRITERIA_NEW:
1673         case CRITERIA_MARKED:
1674         case CRITERIA_DELETED:
1675         case CRITERIA_REPLIED:
1676         case CRITERIA_FORWARDED:
1677         case CRITERIA_LOCKED:
1678         case CRITERIA_SPAM:
1679         case CRITERIA_PARTIAL:
1680         case CRITERIA_IGNORE_THREAD:
1681         case CRITERIA_TAGGED:
1682                 prefs_matcher_disable_widget(matcher.header_combo);
1683                 prefs_matcher_disable_widget(matcher.header_label);
1684                 prefs_matcher_disable_widget(matcher.header_addr_combo);
1685                 prefs_matcher_disable_widget(matcher.header_addr_label);
1686                 prefs_matcher_disable_widget(matcher.value_label);
1687                 prefs_matcher_disable_widget(matcher.value_entry);
1688                 prefs_matcher_disable_widget(matcher.addressbook_folder_label);
1689                 prefs_matcher_disable_widget(matcher.addressbook_folder_combo);
1690                 prefs_matcher_enable_widget(matcher.predicate_label);
1691                 prefs_matcher_disable_widget(matcher.predicate_combo);
1692                 prefs_matcher_enable_widget(matcher.predicate_flag_combo);
1693                 prefs_matcher_disable_widget(matcher.case_checkbtn);
1694                 prefs_matcher_disable_widget(matcher.regexp_checkbtn);
1695                 prefs_matcher_disable_widget(matcher.test_btn);
1696                 prefs_matcher_disable_widget(matcher.addressbook_select_btn);
1697                 break;
1698                 
1699         case CRITERIA_COLORLABEL:
1700                 prefs_matcher_disable_widget(matcher.header_combo);
1701                 prefs_matcher_disable_widget(matcher.header_label);
1702                 prefs_matcher_disable_widget(matcher.header_addr_combo);
1703                 prefs_matcher_disable_widget(matcher.header_addr_label);
1704                 prefs_matcher_enable_widget(matcher.value_label);
1705                 prefs_matcher_disable_widget(matcher.value_entry);
1706                 prefs_matcher_disable_widget(matcher.addressbook_folder_label);
1707                 prefs_matcher_disable_widget(matcher.addressbook_folder_combo);
1708                 prefs_matcher_enable_widget(matcher.predicate_label);
1709                 prefs_matcher_disable_widget(matcher.predicate_combo);
1710                 prefs_matcher_enable_widget(matcher.predicate_flag_combo);
1711                 prefs_matcher_disable_widget(matcher.case_checkbtn);
1712                 prefs_matcher_disable_widget(matcher.regexp_checkbtn);
1713                 prefs_matcher_disable_widget(matcher.test_btn);
1714                 prefs_matcher_disable_widget(matcher.addressbook_select_btn);
1715                 break;
1716
1717         case CRITERIA_SUBJECT:
1718         case CRITERIA_FROM:
1719         case CRITERIA_TO:
1720         case CRITERIA_CC:
1721         case CRITERIA_TO_OR_CC:
1722         case CRITERIA_TAG:
1723         case CRITERIA_NEWSGROUPS:
1724         case CRITERIA_INREPLYTO:
1725         case CRITERIA_REFERENCES:
1726         case CRITERIA_HEADERS_PART:
1727         case CRITERIA_BODY_PART:
1728         case CRITERIA_MESSAGE:
1729                 prefs_matcher_disable_widget(matcher.header_combo);
1730                 prefs_matcher_disable_widget(matcher.header_label);
1731                 prefs_matcher_disable_widget(matcher.header_addr_combo);
1732                 prefs_matcher_disable_widget(matcher.header_addr_label);
1733                 prefs_matcher_enable_widget(matcher.value_label);
1734                 prefs_matcher_enable_widget(matcher.value_entry);
1735                 prefs_matcher_disable_widget(matcher.addressbook_folder_label);
1736                 prefs_matcher_disable_widget(matcher.addressbook_folder_combo);
1737                 prefs_matcher_enable_widget(matcher.predicate_label);
1738                 prefs_matcher_enable_widget(matcher.predicate_combo);
1739                 prefs_matcher_disable_widget(matcher.predicate_flag_combo);
1740                 prefs_matcher_enable_widget(matcher.case_checkbtn);
1741                 prefs_matcher_enable_widget(matcher.regexp_checkbtn);
1742                 prefs_matcher_disable_widget(matcher.test_btn);
1743                 prefs_matcher_disable_widget(matcher.addressbook_select_btn);
1744                 break;
1745
1746         case CRITERIA_TEST:
1747                 prefs_matcher_disable_widget(matcher.header_combo);
1748                 prefs_matcher_disable_widget(matcher.header_label);
1749                 prefs_matcher_disable_widget(matcher.header_addr_combo);
1750                 prefs_matcher_disable_widget(matcher.header_addr_label);
1751                 prefs_matcher_enable_widget(matcher.value_label);
1752                 prefs_matcher_enable_widget(matcher.value_entry);
1753                 prefs_matcher_disable_widget(matcher.addressbook_folder_label);
1754                 prefs_matcher_disable_widget(matcher.addressbook_folder_combo);
1755                 prefs_matcher_enable_widget(matcher.predicate_label);
1756                 prefs_matcher_disable_widget(matcher.predicate_combo);
1757                 prefs_matcher_enable_widget(matcher.predicate_flag_combo);
1758                 prefs_matcher_disable_widget(matcher.case_checkbtn);
1759                 prefs_matcher_disable_widget(matcher.regexp_checkbtn);
1760                 prefs_matcher_enable_widget(matcher.test_btn);
1761                 prefs_matcher_disable_widget(matcher.addressbook_select_btn);
1762                 break;
1763
1764         case CRITERIA_AGE_GREATER:
1765         case CRITERIA_AGE_LOWER:
1766         case CRITERIA_SCORE_GREATER:
1767         case CRITERIA_SCORE_LOWER:
1768         case CRITERIA_SCORE_EQUAL:
1769         case CRITERIA_SIZE_GREATER:
1770         case CRITERIA_SIZE_SMALLER:
1771         case CRITERIA_SIZE_EQUAL:
1772                 prefs_matcher_disable_widget(matcher.header_combo);
1773                 prefs_matcher_disable_widget(matcher.header_label);
1774                 prefs_matcher_disable_widget(matcher.header_addr_combo);
1775                 prefs_matcher_disable_widget(matcher.header_addr_label);
1776                 prefs_matcher_enable_widget(matcher.value_label);
1777                 prefs_matcher_enable_widget(matcher.value_entry);
1778                 prefs_matcher_disable_widget(matcher.addressbook_folder_label);
1779                 prefs_matcher_disable_widget(matcher.addressbook_folder_combo);
1780                 prefs_matcher_disable_widget(matcher.predicate_label);
1781                 prefs_matcher_disable_widget(matcher.predicate_combo);
1782                 prefs_matcher_disable_widget(matcher.predicate_flag_combo);
1783                 prefs_matcher_disable_widget(matcher.case_checkbtn);
1784                 prefs_matcher_disable_widget(matcher.regexp_checkbtn);
1785                 prefs_matcher_disable_widget(matcher.test_btn);
1786                 prefs_matcher_disable_widget(matcher.addressbook_select_btn);
1787                 break;
1788
1789         case CRITERIA_HEADER:
1790                 prefs_matcher_enable_widget(matcher.header_combo);
1791                 prefs_matcher_enable_widget(matcher.header_label);
1792                 prefs_matcher_disable_widget(matcher.header_addr_combo);
1793                 prefs_matcher_disable_widget(matcher.header_addr_label);
1794                 prefs_matcher_enable_widget(matcher.value_label);
1795                 prefs_matcher_enable_widget(matcher.value_entry);
1796                 prefs_matcher_disable_widget(matcher.addressbook_folder_label);
1797                 prefs_matcher_disable_widget(matcher.addressbook_folder_combo);
1798                 prefs_matcher_enable_widget(matcher.predicate_label);
1799                 prefs_matcher_enable_widget(matcher.predicate_combo);
1800                 prefs_matcher_disable_widget(matcher.predicate_flag_combo);
1801                 prefs_matcher_enable_widget(matcher.case_checkbtn);
1802                 prefs_matcher_enable_widget(matcher.regexp_checkbtn);
1803                 prefs_matcher_disable_widget(matcher.test_btn);
1804                 prefs_matcher_disable_widget(matcher.addressbook_select_btn);
1805                 break;
1806
1807         case CRITERIA_FOUND_IN_ADDRESSBOOK:
1808                 prefs_matcher_disable_widget(matcher.header_combo);
1809                 prefs_matcher_disable_widget(matcher.header_label);
1810                 prefs_matcher_enable_widget(matcher.header_addr_combo);
1811                 prefs_matcher_enable_widget(matcher.header_addr_label);
1812                 prefs_matcher_disable_widget(matcher.value_label);
1813                 prefs_matcher_disable_widget(matcher.value_entry);
1814                 prefs_matcher_enable_widget(matcher.addressbook_folder_label);
1815                 prefs_matcher_enable_widget(matcher.addressbook_folder_combo);
1816                 prefs_matcher_enable_widget(matcher.predicate_label);
1817                 prefs_matcher_disable_widget(matcher.predicate_combo);
1818                 prefs_matcher_enable_widget(matcher.predicate_flag_combo);
1819                 prefs_matcher_disable_widget(matcher.case_checkbtn);
1820                 prefs_matcher_disable_widget(matcher.regexp_checkbtn);
1821                 prefs_matcher_disable_widget(matcher.test_btn);
1822                 prefs_matcher_enable_widget(matcher.addressbook_select_btn);
1823                 break;
1824         }
1825 }
1826
1827 /*!
1828  *\brief        Handle key press
1829  *
1830  *\param        widget Widget receiving key press
1831  *\param        event Key event
1832  *\param        data User data
1833  */
1834 static gboolean prefs_matcher_key_pressed(GtkWidget *widget, GdkEventKey *event,
1835                                      gpointer data)
1836 {
1837         if (event && event->keyval == GDK_Escape) {
1838                 prefs_matcher_cancel();
1839                 return TRUE;            
1840         }
1841         return FALSE;
1842 }
1843
1844 /*!
1845  *\brief        Cancel matcher dialog
1846  */
1847 static void prefs_matcher_cancel(void)
1848 {
1849         gtk_widget_hide(matcher.window);
1850         inc_unlock();
1851 }
1852
1853 /*!
1854  *\brief        Accept current matchers
1855  */
1856 static void prefs_matcher_ok(void)
1857 {
1858         MatcherList *matchers;
1859         MatcherProp *matcherprop;
1860         AlertValue val;
1861         gchar *matcher_str = NULL;
1862         gchar *str = NULL;
1863         gint row = 1;
1864         GtkTreeModel *model;
1865         GtkTreeIter iter;
1866
1867         matchers = prefs_matcher_get_list();
1868
1869         if (matchers != NULL) {
1870                 matcherprop = prefs_matcher_dialog_to_matcher();
1871                 if (matcherprop != NULL) {
1872                         str = matcherprop_to_string(matcherprop);
1873                         matcherprop_free(matcherprop);
1874                         if (strcmp(str, "all") != 0) {
1875                                 model = gtk_tree_view_get_model(GTK_TREE_VIEW
1876                                                 (matcher.cond_list_view));
1877
1878                                 while (gtk_tree_model_iter_nth_child(model, &iter, NULL, row)) {
1879                                         gtk_tree_model_get(model, &iter,
1880                                                            PREFS_MATCHER_COND, &matcher_str,
1881                                                            -1);
1882                                         if (matcher_str && strcmp(matcher_str, str) == 0) 
1883                                                 break;
1884                                         row++;
1885                                         g_free(matcher_str);
1886                                         matcher_str = NULL;
1887                                 }
1888
1889                                 if (!matcher_str || strcmp(matcher_str, str) != 0) {
1890                                         val = alertpanel(_("Entry not saved"),
1891                                                  _("The entry was not saved.\nClose anyway?"),
1892                                                  GTK_STOCK_CLOSE, _("+_Continue editing"), NULL);
1893                                         if (G_ALERTDEFAULT != val) {
1894                                                 g_free(matcher_str);                                             
1895                                                 g_free(str);
1896                                                 return;
1897                                         }
1898                                 }
1899                                 g_free(matcher_str);
1900                         }
1901                 }
1902                 g_free(str);
1903                 gtk_widget_hide(matcher.window);
1904                 inc_unlock();
1905                 if (matchers_callback != NULL)
1906                         matchers_callback(matchers);
1907                 matcherlist_free(matchers);
1908         }
1909 }
1910
1911 /*!
1912  *\brief        Called when closing dialog box
1913  *
1914  *\param        widget Dialog widget
1915  *\param        event Event info
1916  *\param        data User data
1917  *
1918  *\return       gint TRUE
1919  */
1920 static gint prefs_matcher_deleted(GtkWidget *widget, GdkEventAny *event,
1921                                   gpointer data)
1922 {
1923         prefs_matcher_cancel();
1924         return TRUE;
1925 }
1926
1927 /*
1928  * Strings describing test format strings
1929  * 
1930  * When adding new lines, remember to put 2 strings for each line
1931  */
1932 static gchar *test_desc_strings[] = {
1933         "%%",   N_("literal %"),
1934         "%s",   N_("Subject"),
1935         "%f",   N_("From"),
1936         "%t",   N_("To"),
1937         "%c",   N_("Cc"),
1938         "%d",   N_("Date"),
1939         "%i",   N_("Message-ID"),
1940         "%n",   N_("Newsgroups"),
1941         "%r",   N_("References"),
1942         "%F",   N_("filename (should not be modified)"),
1943         "\\n",  N_("new line"),
1944         "\\",   N_("escape character for quotes"),
1945         "\\\"", N_("quote character"),
1946         NULL,   NULL
1947 };
1948
1949 static DescriptionWindow test_desc_win = { 
1950         NULL,
1951         NULL, 
1952         2,
1953         N_("Match Type: 'Test'"),
1954         N_("'Test' allows you to test a message or message element "
1955            "using an external program or script. The program will "
1956            "return either 0 or 1.\n\n"
1957            "The following symbols can be used:"),
1958         test_desc_strings
1959 };
1960
1961
1962
1963 /*!
1964  *\brief        Show Test action's info
1965  */
1966 void prefs_matcher_test_info(void)
1967 {
1968         description_window_create(&test_desc_win);
1969 }
1970
1971 void prefs_matcher_addressbook_select(void)
1972 {
1973         gchar *folderpath = NULL;
1974         gboolean ret = FALSE;
1975
1976         folderpath = (gchar *) gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(matcher.addressbook_folder_combo)->entry));
1977         ret = addressbook_folder_selection(&folderpath);
1978         if ( ret != FALSE && folderpath != NULL)
1979                 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(matcher.addressbook_folder_combo)->entry), folderpath);
1980 }
1981
1982
1983 /*
1984  * list view
1985  */
1986
1987 static GtkListStore* prefs_matcher_create_data_store(void)
1988 {
1989         return gtk_list_store_new(N_PREFS_MATCHER_COLUMNS,
1990                                   G_TYPE_STRING,        
1991                                   G_TYPE_BOOLEAN,
1992                                   -1);
1993 }
1994
1995 static void prefs_matcher_list_view_insert_matcher(GtkWidget *list_view,
1996                                                    GtkTreeIter *row_iter,
1997                                                    const gchar *matcher,
1998                                                    gboolean is_valid) 
1999 {
2000         GtkTreeIter iter;
2001         GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model
2002                                         (GTK_TREE_VIEW(list_view)));
2003
2004         if (row_iter == NULL) {
2005                 /* append new */
2006                 gtk_list_store_append(list_store, &iter);
2007         } else {
2008                 /* change existing */
2009                 iter = *row_iter;
2010         }
2011
2012         gtk_list_store_set(list_store, &iter,
2013                            PREFS_MATCHER_COND, matcher,
2014                            PREFS_MATCHER_COND_VALID, is_valid,
2015                            -1);
2016 }
2017
2018 static GtkWidget *prefs_matcher_list_view_create(void)
2019 {
2020         GtkTreeView *list_view;
2021         GtkTreeSelection *selector;
2022         GtkTreeModel *model;
2023
2024         model = GTK_TREE_MODEL(prefs_matcher_create_data_store());
2025         list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
2026         g_object_unref(model);  
2027         
2028         gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
2029         
2030         selector = gtk_tree_view_get_selection(list_view);
2031         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
2032         gtk_tree_selection_set_select_function(selector, prefs_matcher_selected,
2033                                                NULL, NULL);
2034
2035         /* create the columns */
2036         prefs_matcher_create_list_view_columns(GTK_WIDGET(list_view));
2037
2038         return GTK_WIDGET(list_view);
2039 }
2040
2041 static void prefs_matcher_create_list_view_columns(GtkWidget *list_view)
2042 {
2043         GtkTreeViewColumn *column;
2044         GtkCellRenderer *renderer;
2045
2046         renderer = gtk_cell_renderer_text_new();
2047         column = gtk_tree_view_column_new_with_attributes
2048                 (_("Current condition rules"),
2049                  renderer,
2050                  "text", PREFS_MATCHER_COND,
2051                  NULL);
2052         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
2053 }
2054
2055 static gboolean prefs_matcher_selected(GtkTreeSelection *selector,
2056                                        GtkTreeModel *model, 
2057                                        GtkTreePath *path,
2058                                        gboolean currently_selected,
2059                                        gpointer data)
2060 {
2061         gchar *matcher_str;
2062         MatcherProp *prop;
2063         gboolean negative_cond;
2064         gint criteria;
2065         GtkTreeIter iter;
2066         gboolean is_valid;
2067
2068         if (currently_selected)
2069                 return TRUE;
2070
2071         if (!gtk_tree_model_get_iter(model, &iter, path))
2072                 return TRUE;
2073
2074         gtk_tree_model_get(model, &iter, 
2075                            PREFS_MATCHER_COND_VALID,  &is_valid,
2076                            PREFS_MATCHER_COND, &matcher_str,
2077                            -1);
2078         
2079         if (!is_valid) {
2080                 g_free(matcher_str);
2081                 prefs_matcher_reset_condition();
2082                 return TRUE;
2083         }
2084
2085         negative_cond = FALSE;
2086
2087         prop = matcher_parser_get_prop(matcher_str);
2088         if (prop == NULL) {
2089                 g_free(matcher_str);
2090                 return TRUE;
2091         }               
2092
2093         criteria = prefs_matcher_get_criteria_from_matching(prop->criteria);
2094         if (criteria != -1)
2095                 gtk_list_select_item(GTK_LIST(matcher.criteria_list),
2096                                      criteria);
2097
2098         switch(prop->criteria) {
2099         case MATCHCRITERIA_NOT_UNREAD:
2100         case MATCHCRITERIA_NOT_NEW:
2101         case MATCHCRITERIA_NOT_MARKED:
2102         case MATCHCRITERIA_NOT_DELETED:
2103         case MATCHCRITERIA_NOT_REPLIED:
2104         case MATCHCRITERIA_NOT_FORWARDED:
2105         case MATCHCRITERIA_NOT_LOCKED:
2106         case MATCHCRITERIA_NOT_SPAM:
2107         case MATCHCRITERIA_NOT_PARTIAL:
2108         case MATCHCRITERIA_NOT_COLORLABEL:
2109         case MATCHCRITERIA_NOT_IGNORE_THREAD:
2110         case MATCHCRITERIA_NOT_SUBJECT:
2111         case MATCHCRITERIA_NOT_FROM:
2112         case MATCHCRITERIA_NOT_TO:
2113         case MATCHCRITERIA_NOT_CC:
2114         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
2115         case MATCHCRITERIA_NOT_TAG:
2116         case MATCHCRITERIA_NOT_TAGGED:
2117         case MATCHCRITERIA_NOT_NEWSGROUPS:
2118         case MATCHCRITERIA_NOT_INREPLYTO:
2119         case MATCHCRITERIA_NOT_REFERENCES:
2120         case MATCHCRITERIA_NOT_HEADER:
2121         case MATCHCRITERIA_NOT_HEADERS_PART:
2122         case MATCHCRITERIA_NOT_MESSAGE:
2123         case MATCHCRITERIA_NOT_BODY_PART:
2124         case MATCHCRITERIA_NOT_TEST:
2125         case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
2126                 negative_cond = TRUE;
2127                 break;
2128         }
2129         
2130         switch(prop->criteria) {
2131         case MATCHCRITERIA_ALL:
2132                 break;
2133
2134         case MATCHCRITERIA_NOT_SUBJECT:
2135         case MATCHCRITERIA_NOT_FROM:
2136         case MATCHCRITERIA_NOT_TO:
2137         case MATCHCRITERIA_NOT_CC:
2138         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
2139         case MATCHCRITERIA_NOT_TAG:
2140         case MATCHCRITERIA_NOT_NEWSGROUPS:
2141         case MATCHCRITERIA_NOT_INREPLYTO:
2142         case MATCHCRITERIA_NOT_REFERENCES:
2143         case MATCHCRITERIA_NOT_HEADERS_PART:
2144         case MATCHCRITERIA_NOT_BODY_PART:
2145         case MATCHCRITERIA_NOT_MESSAGE:
2146         case MATCHCRITERIA_NOT_TEST:
2147         case MATCHCRITERIA_SUBJECT:
2148         case MATCHCRITERIA_FROM:
2149         case MATCHCRITERIA_TO:
2150         case MATCHCRITERIA_CC:
2151         case MATCHCRITERIA_TO_OR_CC:
2152         case MATCHCRITERIA_TAG:
2153         case MATCHCRITERIA_NEWSGROUPS:
2154         case MATCHCRITERIA_INREPLYTO:
2155         case MATCHCRITERIA_REFERENCES:
2156         case MATCHCRITERIA_HEADERS_PART:
2157         case MATCHCRITERIA_BODY_PART:
2158         case MATCHCRITERIA_MESSAGE:
2159         case MATCHCRITERIA_TEST:
2160                 gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), prop->expr);
2161                 break;
2162
2163         case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
2164         case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
2165         {
2166                 gchar *header;
2167                 gchar *expr;
2168
2169                 /* matcher expressions contain UNtranslated "Any"/"All",
2170                   select the relevant translated combo item */
2171                 if (strcasecmp(prop->header, "All") == 0)
2172                         header = (gchar*)Q_("Filtering Matcher Menu|All");
2173                 else
2174                         if (strcasecmp(prop->header, "Any") == 0)
2175                                 header = _("Any");
2176                         else
2177                                 header = prop->header;
2178                 if (strcasecmp(prop->expr, "Any") == 0)
2179                         expr = _("Any");
2180                 else
2181                         expr = prop->expr;
2182
2183                 gtk_entry_set_text(GTK_ENTRY(matcher.header_addr_entry), header);
2184                 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(matcher.addressbook_folder_combo)->entry), expr);
2185                 break;
2186         }
2187
2188         case MATCHCRITERIA_AGE_GREATER:
2189         case MATCHCRITERIA_AGE_LOWER:
2190         case MATCHCRITERIA_SCORE_GREATER:
2191         case MATCHCRITERIA_SCORE_LOWER:
2192         case MATCHCRITERIA_SCORE_EQUAL:
2193         case MATCHCRITERIA_SIZE_GREATER:
2194         case MATCHCRITERIA_SIZE_SMALLER:
2195         case MATCHCRITERIA_SIZE_EQUAL:
2196                 gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), itos(prop->value));
2197                 break;
2198
2199         case MATCHCRITERIA_NOT_COLORLABEL:
2200         case MATCHCRITERIA_COLORLABEL:
2201                 gtk_option_menu_set_history(GTK_OPTION_MENU(matcher.color_optmenu),
2202                                             prop->value);
2203                 break;
2204
2205         case MATCHCRITERIA_NOT_HEADER:
2206         case MATCHCRITERIA_HEADER:
2207                 gtk_entry_set_text(GTK_ENTRY(matcher.header_entry), prop->header);
2208                 gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), prop->expr);
2209                 break;
2210         }
2211
2212         if (negative_cond) {
2213                 gtk_list_select_item(GTK_LIST(matcher.predicate_list), 1);
2214                 gtk_list_select_item(GTK_LIST(matcher.predicate_flag_list), 1);
2215         } else {
2216                 gtk_list_select_item(GTK_LIST(matcher.predicate_list), 0);
2217                 gtk_list_select_item(GTK_LIST(matcher.predicate_flag_list), 0);
2218         }
2219         
2220         switch(prop->matchtype) {
2221         case MATCHTYPE_MATCH:
2222                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_checkbtn), FALSE);
2223                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_checkbtn), TRUE);
2224                 break;
2225
2226         case MATCHTYPE_MATCHCASE:
2227                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_checkbtn), FALSE);
2228                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_checkbtn), FALSE);
2229                 break;
2230
2231         case MATCHTYPE_REGEXP:
2232                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_checkbtn), TRUE);
2233                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_checkbtn), TRUE);
2234                 break;
2235
2236         case MATCHTYPE_REGEXPCASE:
2237                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_checkbtn), TRUE);
2238                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_checkbtn), FALSE);
2239                 break;
2240         }
2241
2242         g_free(matcher_str);
2243         return TRUE;
2244 }
2245