* configure.ac
[claws.git] / src / prefs_matcher.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #include "intl.h"
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 /*!
54  *\brief        UI data for matcher dialog
55  */
56 static struct Matcher {
57         GtkWidget *window;
58
59         GtkWidget *ok_btn;
60
61         GtkWidget *predicate_combo;
62         GtkWidget *predicate_flag_combo;
63         GtkWidget *header_combo;
64
65         GtkWidget *criteria_list;
66
67         GtkWidget *predicate_list;
68         GtkWidget *predicate_label;
69         GtkWidget *predicate_flag_list;
70
71         GtkWidget *bool_op_list;
72
73         GtkWidget *header_entry;
74         GtkWidget *header_label;
75         GtkWidget *value_entry;
76         GtkWidget *value_label;
77         GtkWidget *case_chkbtn;
78         GtkWidget *regexp_chkbtn;
79         GtkWidget *color_optmenu;
80
81         GtkWidget *exec_btn;
82
83         GtkWidget *cond_clist;
84
85         GtkWidget *criteria_table;
86
87         gint selected_criteria; /*!< selected criteria in combobox */ 
88 } matcher;
89
90 /*!
91  *\brief        Conditions with a negate counterpart (like unread and ~unread)
92  *              have the same CRITERIA_XXX id). I.e. both unread and ~unread
93  *              have criteria id CRITERIA_UNREAD. This id is passed as the
94  *              first parameter to #matcherprop_new and #matcherprop_unquote_new.
95  *
96  *\warning      Currently the enum constants should have the same order as the 
97  *              #criteria_text  
98  */             
99 enum {
100         CRITERIA_ALL = 0,
101
102         CRITERIA_SUBJECT = 1,
103         CRITERIA_FROM = 2,
104         CRITERIA_TO = 3,
105         CRITERIA_CC = 4,
106         CRITERIA_TO_OR_CC = 5,
107         CRITERIA_NEWSGROUPS = 6,
108         CRITERIA_INREPLYTO = 7,
109         CRITERIA_REFERENCES = 8,
110         CRITERIA_AGE_GREATER = 9,
111         CRITERIA_AGE_LOWER = 10,
112         CRITERIA_HEADER = 11,
113         CRITERIA_HEADERS_PART = 12,
114         CRITERIA_BODY_PART = 13,
115         CRITERIA_MESSAGE = 14,
116
117         CRITERIA_UNREAD = 15,
118         CRITERIA_NEW = 16,
119         CRITERIA_MARKED = 17,
120         CRITERIA_DELETED = 18,
121         CRITERIA_REPLIED = 19,
122         CRITERIA_FORWARDED = 20,
123         CRITERIA_LOCKED = 21,
124         CRITERIA_COLORLABEL = 22,
125         CRITERIA_IGNORE_THREAD = 23,
126
127         CRITERIA_SCORE_GREATER = 24,
128         CRITERIA_SCORE_LOWER = 25,
129         CRITERIA_SCORE_EQUAL = 26,
130
131         CRITERIA_EXECUTE = 27,
132
133         CRITERIA_SIZE_GREATER = 28,
134         CRITERIA_SIZE_SMALLER = 29,
135         CRITERIA_SIZE_EQUAL   = 30
136 };
137
138 /*!
139  *\brief        Descriptive text for conditions
140  */
141 static const gchar *criteria_text [] = {
142         N_("All messages"), N_("Subject"),
143         N_("From"), N_("To"), N_("Cc"), N_("To or Cc"),
144         N_("Newsgroups"), N_("In reply to"), N_("References"),
145         N_("Age greater than"), N_("Age lower than"),
146         N_("Header"), N_("Headers part"),
147         N_("Body part"), N_("Whole message"),
148         N_("Unread flag"), N_("New flag"),
149         N_("Marked flag"), N_("Deleted flag"),
150         N_("Replied flag"), N_("Forwarded flag"),
151         N_("Locked flag"),
152         N_("Color label"),
153         N_("Ignore thread"),
154         N_("Score greater than"), N_("Score lower than"),
155         N_("Score equal to"),
156         N_("Execute"),
157         N_("Size greater than"), 
158         N_("Size smaller than"),
159         N_("Size exactly")
160 };
161
162 /*!
163  *\brief        Boolean / predicate constants
164  *
165  *\warning      Same order as #bool_op_text!
166  */
167 enum {
168         BOOL_OP_OR = 0,
169         BOOL_OP_AND = 1
170 };
171
172 /*!
173  *\brief        Descriptive text in UI
174  */
175 static const gchar *bool_op_text [] = {
176         N_("or"), N_("and")
177 };
178
179 /*!
180  *\brief        Contains predicate      
181  *
182  *\warning      Same order as in #predicate_text
183  */
184 enum {
185         PREDICATE_CONTAINS = 0,
186         PREDICATE_DOES_NOT_CONTAIN = 1
187 };
188
189 /*!
190  *\brief        Descriptive text in UI for predicate
191  */
192 static const gchar *predicate_text [] = {
193         N_("contains"), N_("does not contain")
194 };
195
196 /*!
197  *\brief        Enabled predicate
198  *
199  *\warning      Same order as in #predicate_flag_text
200  */
201 enum {
202         PREDICATE_FLAG_ENABLED = 0,
203         PREDICATE_FLAG_DISABLED = 1
204 };
205
206 /*!
207  *\brief        Descriptive text in UI for enabled flag
208  */
209 static const gchar *predicate_flag_text [] = {
210         N_("yes"), N_("no")
211 };
212
213 /*!
214  *\brief        Hooks
215  */
216 static PrefsMatcherSignal *matchers_callback;
217
218 /* widget creating functions */
219 static void prefs_matcher_create        (void);
220
221 static void prefs_matcher_set_dialog    (MatcherList *matchers);
222 static gint prefs_matcher_clist_set_row (gint row, MatcherProp *prop);
223
224 /* callback functions */
225
226 static void prefs_matcher_register_cb   (void);
227 static void prefs_matcher_substitute_cb (void);
228 static void prefs_matcher_delete_cb     (void);
229 static void prefs_matcher_up            (void);
230 static void prefs_matcher_down          (void);
231 static void prefs_matcher_select        (GtkCList       *clist,
232                                          gint            row,
233                                          gint            column,
234                                          GdkEvent       *event);
235 static void prefs_matcher_key_pressed   (GtkWidget      *widget,
236                                          GdkEventKey    *event,
237                                          gpointer        data);
238 static void prefs_matcher_ok            (void);
239 static void prefs_matcher_cancel        (void);
240 static gint prefs_matcher_deleted       (GtkWidget *widget, GdkEventAny *event,
241                                          gpointer data);
242 static void prefs_matcher_criteria_select       (GtkList   *list,
243                                                  GtkWidget *widget,
244                                                  gpointer   user_data);
245 static MatcherList *prefs_matcher_get_list      (void);
246
247
248 /*!
249  *\brief        Find index of list selection 
250  *
251  *\param        list GTK list widget
252  *
253  *\return       gint Selection index
254  */
255 static gint get_sel_from_list(GtkList *list)
256 {
257         gint row = 0;
258         void * sel;
259         GList * child;
260
261         sel = list->selection->data;
262         for (child = list->children; child != NULL; child = g_list_next(child)) {
263                 if (child->data == sel)
264                         return row;
265                 row ++;
266         }
267         
268         return row;
269 }
270
271 /*!
272  *\brief        Opens the matcher dialog with a list of conditions
273  *
274  *\param        matchers List of conditions
275  *\param        cb Callback
276  *
277  */
278 void prefs_matcher_open(MatcherList *matchers, PrefsMatcherSignal *cb)
279 {
280         inc_lock();
281
282         if (!matcher.window) {
283                 prefs_matcher_create();
284         }
285
286         manage_window_set_transient(GTK_WINDOW(matcher.window));
287         gtk_widget_grab_focus(matcher.ok_btn);
288
289         matchers_callback = cb;
290
291         prefs_matcher_set_dialog(matchers);
292
293         gtk_widget_show(matcher.window);
294 }
295
296 /*!
297  *\brief        Create the matcher dialog
298  */
299 static void prefs_matcher_create(void)
300 {
301         GtkWidget *window;
302         GtkWidget *vbox;
303         GtkWidget *ok_btn;
304         GtkWidget *cancel_btn;
305         GtkWidget *confirm_area;
306
307         GtkWidget *vbox1;
308         GtkWidget *vbox2;
309         GtkWidget *vbox3;
310         GtkWidget *criteria_table;
311
312         GtkWidget *hbox1;
313
314         GtkWidget *header_combo;
315         GtkWidget *header_entry;
316         GtkWidget *header_label;
317         GtkWidget *criteria_combo;
318         GtkWidget *criteria_list;
319         GtkWidget *criteria_label;
320         GtkWidget *value_label;
321         GtkWidget *value_entry;
322         GtkWidget *predicate_combo;
323         GtkWidget *predicate_list;
324         GtkWidget *predicate_flag_combo;
325         GtkWidget *predicate_flag_list;
326         GtkWidget *predicate_label;
327         GtkWidget *bool_op_combo;
328         GtkWidget *bool_op_list;
329         GtkWidget *bool_op_label;
330
331         GtkWidget *regexp_chkbtn;
332         GtkWidget *case_chkbtn;
333
334         GtkWidget *reg_hbox;
335         GtkWidget *btn_hbox;
336         GtkWidget *arrow;
337         GtkWidget *reg_btn;
338         GtkWidget *subst_btn;
339         GtkWidget *del_btn;
340
341         GtkWidget *cond_hbox;
342         GtkWidget *cond_scrolledwin;
343         GtkWidget *cond_clist;
344
345         GtkWidget *btn_vbox;
346         GtkWidget *up_btn;
347         GtkWidget *down_btn;
348
349         GtkWidget *exec_btn;
350
351         GtkWidget *color_optmenu;
352
353         GList *combo_items;
354         gint i;
355
356         gchar *title[1];
357
358         debug_print("Creating matcher configuration window...\n");
359
360         window = gtk_window_new(GTK_WINDOW_DIALOG);
361         gtk_container_set_border_width(GTK_CONTAINER(window), 8);
362         gtk_window_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
363         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
364         gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE);
365
366         vbox = gtk_vbox_new(FALSE, 6);
367         gtk_widget_show(vbox);
368         gtk_container_add(GTK_CONTAINER(window), vbox);
369
370         gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"),
371                                 &cancel_btn, _("Cancel"), NULL, NULL);
372         gtk_widget_show(confirm_area);
373         gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
374         gtk_widget_grab_default(ok_btn);
375
376         gtk_window_set_title(GTK_WINDOW(window),
377                              _("Condition configuration"));
378         gtk_signal_connect(GTK_OBJECT(window), "delete_event",
379                            GTK_SIGNAL_FUNC(prefs_matcher_deleted), NULL);
380         gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
381                            GTK_SIGNAL_FUNC(prefs_matcher_key_pressed), NULL);
382         MANAGE_WINDOW_SIGNALS_CONNECT(window);
383         gtk_signal_connect(GTK_OBJECT(ok_btn), "clicked",
384                            GTK_SIGNAL_FUNC(prefs_matcher_ok), NULL);
385         gtk_signal_connect(GTK_OBJECT(cancel_btn), "clicked",
386                            GTK_SIGNAL_FUNC(prefs_matcher_cancel), NULL);
387
388         vbox1 = gtk_vbox_new(FALSE, VSPACING);
389         gtk_widget_show(vbox1);
390         gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0);
391         gtk_container_set_border_width(GTK_CONTAINER (vbox1), 2);
392
393         criteria_table = gtk_table_new(2, 4, FALSE);
394         gtk_widget_show(criteria_table);
395
396         gtk_box_pack_start(GTK_BOX(vbox1), criteria_table, FALSE, FALSE, 0);
397         gtk_table_set_row_spacings(GTK_TABLE(criteria_table), 8);
398         gtk_table_set_col_spacings(GTK_TABLE(criteria_table), 8);
399
400         /* criteria combo box */
401
402         criteria_label = gtk_label_new(_("Match type"));
403         gtk_widget_show(criteria_label);
404         gtk_misc_set_alignment(GTK_MISC(criteria_label), 0, 0.5);
405         gtk_table_attach(GTK_TABLE(criteria_table), criteria_label, 0, 1, 0, 1,
406                          GTK_FILL, 0, 0, 0);
407
408         criteria_combo = gtk_combo_new();
409         gtk_widget_show(criteria_combo);
410
411         combo_items = NULL;
412
413         for (i = 0; i < (gint) (sizeof(criteria_text) / sizeof(gchar *)); i++) {
414                 combo_items = g_list_append(combo_items,
415                                             (gpointer) _(criteria_text[i]));
416         }
417         gtk_combo_set_popdown_strings(GTK_COMBO(criteria_combo), combo_items);
418
419         g_list_free(combo_items);
420
421         gtk_widget_set_usize(criteria_combo, 120, -1);
422         gtk_table_attach(GTK_TABLE(criteria_table), criteria_combo, 0, 1, 1, 2,
423                           0, 0, 0, 0);
424         criteria_list = GTK_COMBO(criteria_combo)->list;
425         gtk_signal_connect(GTK_OBJECT(criteria_list), "select-child",
426                            GTK_SIGNAL_FUNC(prefs_matcher_criteria_select),
427                            NULL);
428
429         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(criteria_combo)->entry),
430                                FALSE);
431
432         /* header name */
433
434         header_label = gtk_label_new(_("Header name"));
435         gtk_widget_show(header_label);
436         gtk_misc_set_alignment(GTK_MISC(header_label), 0, 0.5);
437         gtk_table_attach(GTK_TABLE(criteria_table), header_label, 1, 2, 0, 1,
438                          GTK_FILL, 0, 0, 0);
439
440         header_combo = gtk_combo_new();
441         gtk_widget_show(header_combo);
442         gtk_widget_set_usize(header_combo, 96, -1);
443         gtkut_combo_set_items(GTK_COMBO (header_combo),
444                               "Subject", "From", "To", "Cc", "Reply-To",
445                               "Sender", "X-ML-Name", "X-List", "X-Sequence",
446                               "X-Mailer","X-BeenThere",
447                               NULL);
448         gtk_table_attach(GTK_TABLE(criteria_table), header_combo, 1, 2, 1, 2,
449                          0, 0, 0, 0);
450         header_entry = GTK_COMBO(header_combo)->entry;
451         gtk_entry_set_editable(GTK_ENTRY(header_entry), TRUE);
452
453         /* value */
454
455         value_label = gtk_label_new(_("Value"));
456         gtk_widget_show(value_label);
457         gtk_misc_set_alignment(GTK_MISC (value_label), 0, 0.5);
458         gtk_table_attach(GTK_TABLE(criteria_table), value_label, 2, 3, 0, 1,
459                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
460
461         value_entry = gtk_entry_new();
462         gtk_widget_show(value_entry);
463         gtk_widget_set_usize(value_entry, 200, -1);
464         gtk_table_attach(GTK_TABLE(criteria_table), value_entry, 2, 3, 1, 2,
465                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
466
467         exec_btn = gtk_button_new_with_label(_("Info ..."));
468         gtk_widget_show(exec_btn);
469         gtk_table_attach(GTK_TABLE (criteria_table), exec_btn, 3, 4, 1, 2,
470                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
471         gtk_signal_connect(GTK_OBJECT (exec_btn), "clicked",
472                            GTK_SIGNAL_FUNC (prefs_matcher_exec_info),
473                            NULL);
474
475         color_optmenu = gtk_option_menu_new();
476         gtk_option_menu_set_menu(GTK_OPTION_MENU(color_optmenu),
477                                  colorlabel_create_color_menu());
478
479         /* predicate */
480
481         vbox2 = gtk_vbox_new(FALSE, VSPACING);
482         gtk_widget_show(vbox2);
483         gtk_box_pack_start(GTK_BOX(vbox1), vbox2, FALSE, FALSE, 0);
484
485         hbox1 = gtk_hbox_new(FALSE, 8);
486         gtk_widget_show(hbox1);
487         gtk_box_pack_start(GTK_BOX(vbox2), hbox1, FALSE, FALSE, 0);
488
489         predicate_label = gtk_label_new(_("Predicate"));
490         gtk_widget_show(predicate_label);
491         gtk_box_pack_start(GTK_BOX(hbox1), predicate_label,
492                            FALSE, FALSE, 0);
493
494         predicate_combo = gtk_combo_new();
495         gtk_widget_show(predicate_combo);
496         gtk_widget_set_usize(predicate_combo, 120, -1);
497         predicate_list = GTK_COMBO(predicate_combo)->list;
498         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(predicate_combo)->entry),
499                                FALSE);
500
501         combo_items = NULL;
502
503         for (i = 0; i < (gint) (sizeof(predicate_text) / sizeof(gchar *)); i++) {
504                 combo_items = g_list_append(combo_items,
505                                             (gpointer) _(predicate_text[i]));
506         }
507         gtk_combo_set_popdown_strings(GTK_COMBO(predicate_combo), combo_items);
508
509         g_list_free(combo_items);
510
511         gtk_box_pack_start(GTK_BOX(hbox1), predicate_combo,
512                            FALSE, FALSE, 0);
513
514         /* predicate flag */
515
516         predicate_flag_combo = gtk_combo_new();
517         gtk_widget_hide(predicate_flag_combo);
518         gtk_widget_set_usize(predicate_flag_combo, 120, -1);
519         predicate_flag_list = GTK_COMBO(predicate_flag_combo)->list;
520         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(predicate_flag_combo)->entry), FALSE);
521
522         combo_items = NULL;
523
524         for (i = 0; i < (gint) (sizeof(predicate_text) / sizeof(gchar *)); i++) {
525                 combo_items = g_list_append(combo_items, (gpointer) _(predicate_flag_text[i]));
526         }
527         gtk_combo_set_popdown_strings(GTK_COMBO(predicate_flag_combo),
528                                       combo_items);
529
530         g_list_free(combo_items);
531
532         gtk_box_pack_start(GTK_BOX(hbox1), predicate_flag_combo,
533                            FALSE, FALSE, 0);
534
535         vbox3 = gtk_vbox_new(FALSE, 0);
536         gtk_widget_show (vbox3);
537         gtk_box_pack_start(GTK_BOX(hbox1), vbox3, FALSE, FALSE, 0);
538
539         PACK_CHECK_BUTTON(vbox3, case_chkbtn, _("Case sensitive"));
540         PACK_CHECK_BUTTON(vbox3, regexp_chkbtn, _("Use regexp"));
541
542         /* register / substitute / delete */
543
544         reg_hbox = gtk_hbox_new(FALSE, 4);
545         gtk_widget_show(reg_hbox);
546         gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0);
547
548         arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
549         gtk_widget_show(arrow);
550         gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0);
551         gtk_widget_set_usize(arrow, -1, 16);
552
553         btn_hbox = gtk_hbox_new(TRUE, 4);
554         gtk_widget_show(btn_hbox);
555         gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0);
556
557         reg_btn = gtk_button_new_with_label(_("Add"));
558         gtk_widget_show(reg_btn);
559         gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0);
560         gtk_signal_connect(GTK_OBJECT(reg_btn), "clicked",
561                            GTK_SIGNAL_FUNC(prefs_matcher_register_cb), NULL);
562
563         subst_btn = gtk_button_new_with_label(_("  Replace  "));
564         gtk_widget_show(subst_btn);
565         gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0);
566         gtk_signal_connect(GTK_OBJECT(subst_btn), "clicked",
567                            GTK_SIGNAL_FUNC(prefs_matcher_substitute_cb),
568                            NULL);
569
570         del_btn = gtk_button_new_with_label(_("Delete"));
571         gtk_widget_show(del_btn);
572         gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0);
573         gtk_signal_connect(GTK_OBJECT(del_btn), "clicked",
574                            GTK_SIGNAL_FUNC(prefs_matcher_delete_cb), NULL);
575
576         /* boolean operation */
577
578         bool_op_label = gtk_label_new(_("Boolean Op"));
579         gtk_misc_set_alignment(GTK_MISC(value_label), 0, 0.5);
580         gtk_widget_show(bool_op_label);
581         gtk_box_pack_start(GTK_BOX(btn_hbox), bool_op_label,
582                            FALSE, FALSE, 0);
583
584         bool_op_combo = gtk_combo_new();
585         gtk_widget_show(bool_op_combo);
586         gtk_widget_set_usize(bool_op_combo, 50, -1);
587         bool_op_list = GTK_COMBO(bool_op_combo)->list;
588         gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(bool_op_combo)->entry),
589                                FALSE);
590
591         combo_items = NULL;
592
593         for (i = 0; i < (gint) (sizeof(bool_op_text) / sizeof(gchar *)); i++) {
594                 combo_items = g_list_append(combo_items,
595                                             (gpointer) _(bool_op_text[i]));
596         }
597         gtk_combo_set_popdown_strings(GTK_COMBO(bool_op_combo), combo_items);
598
599         g_list_free(combo_items);
600
601         gtk_box_pack_start(GTK_BOX(btn_hbox), bool_op_combo,
602                            FALSE, FALSE, 0);
603
604         cond_hbox = gtk_hbox_new(FALSE, 8);
605         gtk_widget_show(cond_hbox);
606         gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0);
607
608         cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
609         gtk_widget_show(cond_scrolledwin);
610         gtk_widget_set_usize(cond_scrolledwin, -1, 150);
611         gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin,
612                            TRUE, TRUE, 0);
613         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cond_scrolledwin),
614                                        GTK_POLICY_AUTOMATIC,
615                                        GTK_POLICY_AUTOMATIC);
616
617         title[0] = _("Current condition rules");
618         cond_clist = gtk_clist_new_with_titles(1, title);
619         gtk_widget_show(cond_clist);
620         gtk_container_add(GTK_CONTAINER(cond_scrolledwin), cond_clist);
621         gtk_clist_set_column_width(GTK_CLIST(cond_clist), 0, 80);
622         gtk_clist_set_selection_mode(GTK_CLIST(cond_clist),
623                                      GTK_SELECTION_BROWSE);
624         GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(cond_clist)->column[0].button,
625                                GTK_CAN_FOCUS);
626         gtk_signal_connect(GTK_OBJECT(cond_clist), "select_row",
627                            GTK_SIGNAL_FUNC(prefs_matcher_select), NULL);
628
629         btn_vbox = gtk_vbox_new(FALSE, 8);
630         gtk_widget_show(btn_vbox);
631         gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0);
632
633         up_btn = gtk_button_new_with_label(_("Up"));
634         gtk_widget_show(up_btn);
635         gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0);
636         gtk_signal_connect(GTK_OBJECT(up_btn), "clicked",
637                            GTK_SIGNAL_FUNC(prefs_matcher_up), NULL);
638
639         down_btn = gtk_button_new_with_label(_("Down"));
640         gtk_widget_show(down_btn);
641         gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0);
642         gtk_signal_connect(GTK_OBJECT(down_btn), "clicked",
643                            GTK_SIGNAL_FUNC(prefs_matcher_down), NULL);
644
645         gtk_widget_show_all(window);
646
647         matcher.window    = window;
648
649         matcher.ok_btn = ok_btn;
650
651         matcher.criteria_list = criteria_list;
652         matcher.header_combo = header_combo;
653         matcher.header_entry = header_entry;
654         matcher.header_label = header_label;
655         matcher.value_entry = value_entry;
656         matcher.value_label = value_label;
657         matcher.predicate_label = predicate_label;
658         matcher.predicate_list = predicate_list;
659         matcher.predicate_combo = predicate_combo;
660         matcher.predicate_flag_list = predicate_flag_list;
661         matcher.predicate_flag_combo = predicate_flag_combo;
662         matcher.case_chkbtn = case_chkbtn;
663         matcher.regexp_chkbtn = regexp_chkbtn;
664         matcher.bool_op_list = bool_op_list;
665         matcher.exec_btn = exec_btn;
666         matcher.color_optmenu = color_optmenu;
667         matcher.criteria_table = criteria_table;
668
669         matcher.cond_clist = cond_clist;
670 }
671
672 /*!
673  *\brief        Set the contents of a row
674  *
675  *\param        row Index of row to set
676  *\param        prop Condition to set
677  *
678  *\return       gint Row index \a prop has been added
679  */
680 static gint prefs_matcher_clist_set_row(gint row, MatcherProp *prop)
681 {
682         GtkCList *clist = GTK_CLIST(matcher.cond_clist);
683         gchar *cond_str[1];
684         gchar *matcher_str;
685
686         if (prop == NULL) {
687                 cond_str[0] = _("(New)");
688                 return gtk_clist_append(clist, cond_str);
689         }
690
691         matcher_str = matcherprop_to_string(prop);
692         cond_str[0] = matcher_str;
693         if (row < 0)
694                 row = gtk_clist_append(clist, cond_str);
695         else
696                 gtk_clist_set_text(clist, row, 0, cond_str[0]);
697         g_free(matcher_str);
698
699         return row;
700 }
701
702 /*!
703  *\brief        Clears a condition in the list widget
704  */
705 static void prefs_matcher_reset_condition(void)
706 {
707         gtk_list_select_item(GTK_LIST(matcher.criteria_list), 0);
708         gtk_list_select_item(GTK_LIST(matcher.predicate_list), 0);
709         gtk_entry_set_text(GTK_ENTRY(matcher.header_entry), "");
710         gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), "");
711 }
712
713 /*!
714  *\brief        Update scrollbar
715  */
716 static void prefs_matcher_update_hscrollbar(void)
717 {
718         gint optwidth = gtk_clist_optimal_column_width(GTK_CLIST(matcher.cond_clist), 0);
719         gtk_clist_set_column_width(GTK_CLIST(matcher.cond_clist), 0, optwidth);
720 }
721
722 /*!
723  *\brief        Initializes dialog with a set of conditions
724  *
725  *\param        matchers List of conditions
726  */
727 static void prefs_matcher_set_dialog(MatcherList *matchers)
728 {
729         GtkCList *clist = GTK_CLIST(matcher.cond_clist);
730         GSList *cur;
731         gboolean bool_op = 1;
732
733         gtk_clist_freeze(clist);
734         gtk_clist_clear(clist);
735
736         prefs_matcher_clist_set_row(-1, NULL);
737         if (matchers != NULL) {
738                 for (cur = matchers->matchers; cur != NULL;
739                      cur = g_slist_next(cur)) {
740                         MatcherProp *prop;
741                         prop = (MatcherProp *) cur->data;
742                         prefs_matcher_clist_set_row(-1, prop);
743                 }
744
745                 bool_op = matchers->bool_and;
746         }
747         
748         prefs_matcher_update_hscrollbar();
749
750         gtk_clist_thaw(clist);
751
752         gtk_list_select_item(GTK_LIST(matcher.bool_op_list), bool_op);
753
754         prefs_matcher_reset_condition();
755 }
756
757 /*!
758  *\brief        Converts current conditions in list box in
759  *              a matcher list used by the matcher.
760  *
761  *\return       MatcherList * List of conditions.
762  */
763 static MatcherList *prefs_matcher_get_list(void)
764 {
765         gchar *matcher_str;
766         MatcherProp *prop;
767         gint row = 1;
768         gboolean bool_and;
769         GSList *matcher_list;
770         MatcherList *matchers;
771
772         matcher_list = NULL;
773
774         while (gtk_clist_get_text(GTK_CLIST(matcher.cond_clist),
775                                   row, 0, &matcher_str)) {
776
777                 if (strcmp(matcher_str, _("(New)")) != 0) {
778                         /* tmp = matcher_str; */
779                         prop = matcher_parser_get_prop(matcher_str);
780                         
781                         if (prop == NULL)
782                                 break;
783                         
784                         matcher_list = g_slist_append(matcher_list, prop);
785                 }
786                 row ++;
787         }
788
789         bool_and = get_sel_from_list(GTK_LIST(matcher.bool_op_list));
790
791         matchers = matcherlist_new(matcher_list, bool_and);
792
793         return matchers;
794 }
795
796 /*!
797  *\brief        Maps a keyword id (see #get_matchparser_tab_id) to a 
798  *              criteria type (see first parameter of #matcherprop_new
799  *              or #matcherprop_unquote_new)
800  *
801  *\param        matching_id Id returned by the matcher parser.
802  *
803  *\return       gint One of the CRITERIA_xxx constants.
804  */
805 static gint prefs_matcher_get_criteria_from_matching(gint matching_id)
806 {
807         switch(matching_id) {
808         case MATCHCRITERIA_ALL:
809                 return CRITERIA_ALL;
810         case MATCHCRITERIA_NOT_UNREAD:
811         case MATCHCRITERIA_UNREAD:
812                 return CRITERIA_UNREAD;
813         case MATCHCRITERIA_NOT_NEW:
814         case MATCHCRITERIA_NEW:
815                 return CRITERIA_NEW;
816         case MATCHCRITERIA_NOT_MARKED:
817         case MATCHCRITERIA_MARKED:
818                 return CRITERIA_MARKED;
819         case MATCHCRITERIA_NOT_DELETED:
820         case MATCHCRITERIA_DELETED:
821                 return CRITERIA_DELETED;
822         case MATCHCRITERIA_NOT_REPLIED:
823         case MATCHCRITERIA_REPLIED:
824                 return CRITERIA_REPLIED;
825         case MATCHCRITERIA_NOT_FORWARDED:
826         case MATCHCRITERIA_FORWARDED:
827                 return CRITERIA_FORWARDED;
828         case MATCHCRITERIA_LOCKED:
829         case MATCHCRITERIA_NOT_LOCKED:
830                 return CRITERIA_LOCKED;
831         case MATCHCRITERIA_COLORLABEL:
832         case MATCHCRITERIA_NOT_COLORLABEL:
833                 return CRITERIA_COLORLABEL;
834         case MATCHCRITERIA_IGNORE_THREAD:
835         case MATCHCRITERIA_NOT_IGNORE_THREAD:
836                 return CRITERIA_IGNORE_THREAD;
837         case MATCHCRITERIA_NOT_SUBJECT:
838         case MATCHCRITERIA_SUBJECT:
839                 return CRITERIA_SUBJECT;
840         case MATCHCRITERIA_NOT_FROM:
841         case MATCHCRITERIA_FROM:
842                 return CRITERIA_FROM;
843         case MATCHCRITERIA_NOT_TO:
844         case MATCHCRITERIA_TO:
845                 return CRITERIA_TO;
846         case MATCHCRITERIA_NOT_CC:
847         case MATCHCRITERIA_CC:
848                 return CRITERIA_CC;
849         case MATCHCRITERIA_NOT_NEWSGROUPS:
850         case MATCHCRITERIA_NEWSGROUPS:
851                 return CRITERIA_NEWSGROUPS;
852         case MATCHCRITERIA_NOT_INREPLYTO:
853         case MATCHCRITERIA_INREPLYTO:
854                 return CRITERIA_INREPLYTO;
855         case MATCHCRITERIA_NOT_REFERENCES:
856         case MATCHCRITERIA_REFERENCES:
857                 return CRITERIA_REFERENCES;
858         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
859         case MATCHCRITERIA_TO_OR_CC:
860                 return CRITERIA_TO_OR_CC;
861         case MATCHCRITERIA_NOT_BODY_PART:
862         case MATCHCRITERIA_BODY_PART:
863                 return CRITERIA_BODY_PART;
864         case MATCHCRITERIA_NOT_MESSAGE:
865         case MATCHCRITERIA_MESSAGE:
866                 return CRITERIA_MESSAGE;
867                 break;
868         case MATCHCRITERIA_NOT_HEADERS_PART:
869         case MATCHCRITERIA_HEADERS_PART:
870                 return CRITERIA_HEADERS_PART;
871         case MATCHCRITERIA_NOT_HEADER:
872         case MATCHCRITERIA_HEADER:
873                 return CRITERIA_HEADER;
874         case MATCHCRITERIA_AGE_GREATER:
875                 return CRITERIA_AGE_GREATER;
876         case MATCHCRITERIA_AGE_LOWER:
877                 return CRITERIA_AGE_LOWER;
878         case MATCHCRITERIA_SCORE_GREATER:
879                 return CRITERIA_SCORE_GREATER;
880         case MATCHCRITERIA_SCORE_LOWER:
881                 return CRITERIA_SCORE_LOWER;
882         case MATCHCRITERIA_SCORE_EQUAL:
883                 return CRITERIA_SCORE_EQUAL;
884         case MATCHCRITERIA_NOT_EXECUTE:
885         case MATCHCRITERIA_EXECUTE:
886                 return CRITERIA_EXECUTE;
887         case MATCHCRITERIA_SIZE_GREATER:
888                 return CRITERIA_SIZE_GREATER;
889         case MATCHCRITERIA_SIZE_SMALLER:
890                 return CRITERIA_SIZE_SMALLER;
891         case MATCHCRITERIA_SIZE_EQUAL:
892                 return CRITERIA_SIZE_EQUAL;
893         default:
894                 return -1;
895         }
896 }
897
898 /*!
899  *\brief        Returns the matcher keyword id from a criteria id
900  *
901  *\param        criteria_id Criteria id (should not be the negate
902  *              one)
903  *
904  *\return       gint A matcher keyword id. See #get_matchparser_tab_id.
905  */
906 static gint prefs_matcher_get_matching_from_criteria(gint criteria_id)
907 {
908         switch (criteria_id) {
909         case CRITERIA_ALL:
910                 return MATCHCRITERIA_ALL;
911         case CRITERIA_UNREAD:
912                 return MATCHCRITERIA_UNREAD;
913         case CRITERIA_NEW:
914                 return MATCHCRITERIA_NEW;
915         case CRITERIA_MARKED:
916                 return MATCHCRITERIA_MARKED;
917         case CRITERIA_DELETED:
918                 return MATCHCRITERIA_DELETED;
919         case CRITERIA_REPLIED:
920                 return MATCHCRITERIA_REPLIED;
921         case CRITERIA_FORWARDED:
922                 return MATCHCRITERIA_FORWARDED;
923         case CRITERIA_LOCKED:
924                 return MATCHCRITERIA_LOCKED;
925         case CRITERIA_COLORLABEL:
926                 return MATCHCRITERIA_COLORLABEL;
927         case CRITERIA_IGNORE_THREAD:
928                 return MATCHCRITERIA_IGNORE_THREAD;
929         case CRITERIA_SUBJECT:
930                 return MATCHCRITERIA_SUBJECT;
931         case CRITERIA_FROM:
932                 return MATCHCRITERIA_FROM;
933         case CRITERIA_TO:
934                 return MATCHCRITERIA_TO;
935         case CRITERIA_CC:
936                 return MATCHCRITERIA_CC;
937         case CRITERIA_TO_OR_CC:
938                 return MATCHCRITERIA_TO_OR_CC;
939         case CRITERIA_NEWSGROUPS:
940                 return MATCHCRITERIA_NEWSGROUPS;
941         case CRITERIA_INREPLYTO:
942                 return MATCHCRITERIA_INREPLYTO;
943         case CRITERIA_REFERENCES:
944                 return MATCHCRITERIA_REFERENCES;
945         case CRITERIA_AGE_GREATER:
946                 return MATCHCRITERIA_AGE_GREATER;
947         case CRITERIA_AGE_LOWER:
948                 return MATCHCRITERIA_AGE_LOWER;
949         case CRITERIA_SCORE_GREATER:
950                 return MATCHCRITERIA_SCORE_GREATER;
951         case CRITERIA_SCORE_LOWER:
952                 return MATCHCRITERIA_SCORE_LOWER;
953         case CRITERIA_SCORE_EQUAL:
954                 return MATCHCRITERIA_SCORE_EQUAL;
955         case CRITERIA_HEADER:
956                 return MATCHCRITERIA_HEADER;
957         case CRITERIA_HEADERS_PART:
958                 return MATCHCRITERIA_HEADERS_PART;
959         case CRITERIA_BODY_PART:
960                 return MATCHCRITERIA_BODY_PART;
961         case CRITERIA_MESSAGE:
962                 return MATCHCRITERIA_MESSAGE;
963         case CRITERIA_EXECUTE:
964                 return MATCHCRITERIA_EXECUTE;
965         case CRITERIA_SIZE_GREATER:
966                 return MATCHCRITERIA_SIZE_GREATER;
967         case CRITERIA_SIZE_SMALLER:
968                 return MATCHCRITERIA_SIZE_SMALLER;
969         case CRITERIA_SIZE_EQUAL:
970                 return MATCHCRITERIA_SIZE_EQUAL;
971         default:
972                 return -1;
973         }
974 }
975
976 /*!
977  *\brief        Returns the negate matcher keyword id from a matcher keyword
978  *              id.
979  *
980  *\param        matcher_criteria Matcher keyword id. 
981  *
982  *\return       gint A matcher keyword id. See #get_matchparser_tab_id.
983  */
984 static gint prefs_matcher_not_criteria(gint matcher_criteria)
985 {
986         switch(matcher_criteria) {
987         case MATCHCRITERIA_UNREAD:
988                 return MATCHCRITERIA_NOT_UNREAD;
989         case MATCHCRITERIA_NEW:
990                 return MATCHCRITERIA_NOT_NEW;
991         case MATCHCRITERIA_MARKED:
992                 return MATCHCRITERIA_NOT_MARKED;
993         case MATCHCRITERIA_DELETED:
994                 return MATCHCRITERIA_NOT_DELETED;
995         case MATCHCRITERIA_REPLIED:
996                 return MATCHCRITERIA_NOT_REPLIED;
997         case MATCHCRITERIA_FORWARDED:
998                 return MATCHCRITERIA_NOT_FORWARDED;
999         case MATCHCRITERIA_LOCKED:
1000                 return MATCHCRITERIA_NOT_LOCKED;
1001         case MATCHCRITERIA_COLORLABEL:
1002                 return MATCHCRITERIA_NOT_COLORLABEL;
1003         case MATCHCRITERIA_IGNORE_THREAD:
1004                 return MATCHCRITERIA_NOT_IGNORE_THREAD;
1005         case MATCHCRITERIA_SUBJECT:
1006                 return MATCHCRITERIA_NOT_SUBJECT;
1007         case MATCHCRITERIA_FROM:
1008                 return MATCHCRITERIA_NOT_FROM;
1009         case MATCHCRITERIA_TO:
1010                 return MATCHCRITERIA_NOT_TO;
1011         case MATCHCRITERIA_CC:
1012                 return MATCHCRITERIA_NOT_CC;
1013         case MATCHCRITERIA_TO_OR_CC:
1014                 return MATCHCRITERIA_NOT_TO_AND_NOT_CC;
1015         case MATCHCRITERIA_NEWSGROUPS:
1016                 return MATCHCRITERIA_NOT_NEWSGROUPS;
1017         case MATCHCRITERIA_INREPLYTO:
1018                 return MATCHCRITERIA_NOT_INREPLYTO;
1019         case MATCHCRITERIA_REFERENCES:
1020                 return MATCHCRITERIA_NOT_REFERENCES;
1021         case MATCHCRITERIA_HEADER:
1022                 return MATCHCRITERIA_NOT_HEADER;
1023         case MATCHCRITERIA_HEADERS_PART:
1024                 return MATCHCRITERIA_NOT_HEADERS_PART;
1025         case MATCHCRITERIA_MESSAGE:
1026                 return MATCHCRITERIA_NOT_MESSAGE;
1027         case MATCHCRITERIA_EXECUTE:
1028                 return MATCHCRITERIA_NOT_EXECUTE;
1029         case MATCHCRITERIA_BODY_PART:
1030                 return MATCHCRITERIA_NOT_BODY_PART;
1031         default:
1032                 return matcher_criteria;
1033         }
1034 }
1035
1036 /*!
1037  *\brief        Converts the text in the selected row to a 
1038  *              matcher structure
1039  *
1040  *\return       MatcherProp * Newly allocated matcher structure.
1041  */
1042 static MatcherProp *prefs_matcher_dialog_to_matcher(void)
1043 {
1044         MatcherProp *matcherprop;
1045         gint criteria;
1046         gint matchtype;
1047         gint value_pred;
1048         gint value_pred_flag;
1049         gint value_criteria;
1050         gboolean use_regexp;
1051         gboolean case_sensitive;
1052         gchar *header;
1053         gchar *expr;
1054         gint value;
1055         gchar *value_str;
1056
1057         value_criteria = get_sel_from_list(GTK_LIST(matcher.criteria_list));
1058
1059         criteria = prefs_matcher_get_matching_from_criteria(value_criteria);
1060
1061         value_pred = get_sel_from_list(GTK_LIST(matcher.predicate_list));
1062         value_pred_flag = get_sel_from_list(GTK_LIST(matcher.predicate_flag_list));
1063
1064         use_regexp = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(matcher.regexp_chkbtn));
1065         case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(matcher.case_chkbtn));
1066
1067         switch (value_criteria) {
1068         case CRITERIA_UNREAD:
1069         case CRITERIA_NEW:
1070         case CRITERIA_MARKED:
1071         case CRITERIA_DELETED:
1072         case CRITERIA_REPLIED:
1073         case CRITERIA_FORWARDED:
1074         case CRITERIA_LOCKED:
1075         case CRITERIA_EXECUTE:
1076         case CRITERIA_COLORLABEL:
1077         case CRITERIA_IGNORE_THREAD:
1078                 if (value_pred_flag == PREDICATE_FLAG_DISABLED)
1079                         criteria = prefs_matcher_not_criteria(criteria);
1080                 break;
1081         case CRITERIA_SUBJECT:
1082         case CRITERIA_FROM:
1083         case CRITERIA_TO:
1084         case CRITERIA_CC:
1085         case CRITERIA_TO_OR_CC:
1086         case CRITERIA_NEWSGROUPS:
1087         case CRITERIA_INREPLYTO:
1088         case CRITERIA_REFERENCES:
1089         case CRITERIA_HEADERS_PART:
1090         case CRITERIA_BODY_PART:
1091         case CRITERIA_MESSAGE:
1092         case CRITERIA_AGE_GREATER:
1093         case CRITERIA_AGE_LOWER:
1094         case CRITERIA_HEADER:
1095                 if (value_pred == PREDICATE_DOES_NOT_CONTAIN)
1096                         criteria = prefs_matcher_not_criteria(criteria);
1097                 break;
1098         }
1099
1100         if (use_regexp) {
1101                 if (case_sensitive)
1102                         matchtype = MATCHTYPE_REGEXP;
1103                 else
1104                         matchtype = MATCHTYPE_REGEXPCASE;
1105         }
1106         else {
1107                 if (case_sensitive)
1108                         matchtype = MATCHTYPE_MATCH;
1109                 else
1110                         matchtype = MATCHTYPE_MATCHCASE;
1111         }
1112
1113         header = NULL;
1114         expr = NULL;
1115         value = 0;
1116
1117         switch (value_criteria) {
1118         case CRITERIA_ALL:
1119         case CRITERIA_UNREAD:
1120         case CRITERIA_NEW:
1121         case CRITERIA_MARKED:
1122         case CRITERIA_DELETED:
1123         case CRITERIA_REPLIED:
1124         case CRITERIA_FORWARDED:
1125         case CRITERIA_LOCKED:
1126         case CRITERIA_IGNORE_THREAD:
1127                 break;
1128
1129         case CRITERIA_SUBJECT:
1130         case CRITERIA_FROM:
1131         case CRITERIA_TO:
1132         case CRITERIA_CC:
1133         case CRITERIA_TO_OR_CC:
1134         case CRITERIA_NEWSGROUPS:
1135         case CRITERIA_INREPLYTO:
1136         case CRITERIA_REFERENCES:
1137         case CRITERIA_HEADERS_PART:
1138         case CRITERIA_BODY_PART:
1139         case CRITERIA_MESSAGE:
1140         case CRITERIA_EXECUTE:
1141                 expr = gtk_entry_get_text(GTK_ENTRY(matcher.value_entry));
1142                 break;
1143
1144         case CRITERIA_AGE_GREATER:
1145         case CRITERIA_AGE_LOWER:
1146         case CRITERIA_SCORE_GREATER:
1147         case CRITERIA_SCORE_LOWER:
1148         case CRITERIA_SCORE_EQUAL:
1149         case CRITERIA_SIZE_GREATER:
1150         case CRITERIA_SIZE_SMALLER:
1151         case CRITERIA_SIZE_EQUAL:
1152                 value_str = gtk_entry_get_text(GTK_ENTRY(matcher.value_entry));
1153
1154                 if (*value_str == '\0') {
1155                     alertpanel_error(_("Value is not set."));
1156                     return NULL;
1157                 }
1158
1159                 value = atoi(value_str);
1160
1161                 break;
1162                 
1163         case CRITERIA_COLORLABEL:
1164                 value = colorlabel_get_color_menu_active_item
1165                         (gtk_option_menu_get_menu(GTK_OPTION_MENU
1166                                 (matcher.color_optmenu))); 
1167                 break;
1168
1169         case CRITERIA_HEADER:
1170
1171                 header = gtk_entry_get_text(GTK_ENTRY(matcher.header_entry));
1172                 expr = gtk_entry_get_text(GTK_ENTRY(matcher.value_entry));
1173
1174                 if (*header == '\0') {
1175                     alertpanel_error(_("Header name is not set."));
1176                     return NULL;
1177                 }
1178                 break;
1179         }
1180
1181         matcherprop = matcherprop_new(criteria, header, matchtype,
1182                                       expr, value);
1183
1184         return matcherprop;
1185 }
1186
1187 /*!
1188  *\brief        Signal handler for register button
1189  */
1190 static void prefs_matcher_register_cb(void)
1191 {
1192         MatcherProp *matcherprop;
1193         
1194         matcherprop = prefs_matcher_dialog_to_matcher();
1195         if (matcherprop == NULL)
1196                 return;
1197
1198         prefs_matcher_clist_set_row(-1, matcherprop);
1199
1200         matcherprop_free(matcherprop);
1201
1202         prefs_matcher_reset_condition();
1203         prefs_matcher_update_hscrollbar();
1204 }
1205
1206 /*!
1207  *\brief        Signal handler for substitute button
1208  */
1209 static void prefs_matcher_substitute_cb(void)
1210 {
1211         GtkCList *clist = GTK_CLIST(matcher.cond_clist);
1212         gint row;
1213         MatcherProp *matcherprop;
1214
1215         if (!clist->selection) return;
1216         row = GPOINTER_TO_INT(clist->selection->data);
1217         if (row == 0)
1218                 return;
1219         
1220         matcherprop = prefs_matcher_dialog_to_matcher();
1221         if (matcherprop == NULL)
1222                 return;
1223
1224         prefs_matcher_clist_set_row(row, matcherprop);
1225
1226         matcherprop_free(matcherprop);
1227
1228         prefs_matcher_reset_condition();
1229         
1230         prefs_matcher_update_hscrollbar();
1231 }
1232
1233 /*!
1234  *\brief        Signal handler for delete button
1235  */
1236 static void prefs_matcher_delete_cb(void)
1237 {
1238         GtkCList *clist = GTK_CLIST(matcher.cond_clist);
1239         gint row;
1240
1241         if (!clist->selection) return;
1242         row = GPOINTER_TO_INT(clist->selection->data);
1243         if (row == 0)
1244                 return;
1245
1246         gtk_clist_remove(clist, row);
1247
1248         prefs_matcher_reset_condition();
1249
1250         prefs_matcher_update_hscrollbar();
1251 }
1252
1253 /*!
1254  *\brief        Signal handler for 'move up' button
1255  */
1256 static void prefs_matcher_up(void)
1257 {
1258         GtkCList *clist = GTK_CLIST(matcher.cond_clist);
1259         gint row;
1260
1261         if (!clist->selection) return;
1262
1263         row = GPOINTER_TO_INT(clist->selection->data);
1264         if (row > 1) {
1265                 gtk_clist_row_move(clist, row, row - 1);
1266                 if (gtk_clist_row_is_visible(clist, row - 1) != GTK_VISIBILITY_FULL)
1267                         gtk_clist_moveto(clist, row - 1, 0, 0, 0);
1268         }
1269 }
1270
1271 /*!
1272  *\brief        Signal handler for 'move down' button
1273  */
1274 static void prefs_matcher_down(void)
1275 {
1276         GtkCList *clist = GTK_CLIST(matcher.cond_clist);
1277         gint row;
1278
1279         if (!clist->selection) return;
1280
1281         row = GPOINTER_TO_INT(clist->selection->data);
1282         if (row >= 1 && row < clist->rows - 1) {
1283                 gtk_clist_row_move(clist, row, row + 1);
1284                 if (gtk_clist_row_is_visible(clist, row + 1) != GTK_VISIBILITY_FULL)
1285                         gtk_clist_moveto(clist, row + 1, 0, 1, 0);
1286         }
1287 }
1288
1289 /*!
1290  *\brief        Signal handler for select row.
1291  *
1292  *\param        clist List widget
1293  *\param        row Selected row
1294  *\param        column Selected column
1295  *\param        event Event information
1296  */
1297 static void prefs_matcher_select(GtkCList *clist, gint row, gint column,
1298                                  GdkEvent *event)
1299 {
1300         gchar *matcher_str;
1301         MatcherProp *prop;
1302         gboolean negative_cond;
1303         gint criteria;
1304
1305         if (!gtk_clist_get_text(GTK_CLIST(matcher.cond_clist),
1306                                 row, 0, &matcher_str))
1307                 return;
1308
1309         negative_cond = FALSE;
1310
1311         if (row == 0) {
1312                 prefs_matcher_reset_condition();
1313                 return;
1314         }
1315
1316         prop = matcher_parser_get_prop(matcher_str);
1317         if (prop == NULL)
1318                 return;
1319
1320         criteria = prefs_matcher_get_criteria_from_matching(prop->criteria);
1321         if (criteria != -1)
1322                 gtk_list_select_item(GTK_LIST(matcher.criteria_list),
1323                                      criteria);
1324
1325         switch(prop->criteria) {
1326         case MATCHCRITERIA_NOT_UNREAD:
1327         case MATCHCRITERIA_NOT_NEW:
1328         case MATCHCRITERIA_NOT_MARKED:
1329         case MATCHCRITERIA_NOT_DELETED:
1330         case MATCHCRITERIA_NOT_REPLIED:
1331         case MATCHCRITERIA_NOT_FORWARDED:
1332         case MATCHCRITERIA_NOT_LOCKED:
1333         case MATCHCRITERIA_NOT_COLORLABEL:
1334         case MATCHCRITERIA_NOT_IGNORE_THREAD:
1335         case MATCHCRITERIA_NOT_SUBJECT:
1336         case MATCHCRITERIA_NOT_FROM:
1337         case MATCHCRITERIA_NOT_TO:
1338         case MATCHCRITERIA_NOT_CC:
1339         case MATCHCRITERIA_NOT_NEWSGROUPS:
1340         case MATCHCRITERIA_NOT_INREPLYTO:
1341         case MATCHCRITERIA_NOT_REFERENCES:
1342         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1343         case MATCHCRITERIA_NOT_BODY_PART:
1344         case MATCHCRITERIA_NOT_MESSAGE:
1345         case MATCHCRITERIA_NOT_HEADERS_PART:
1346         case MATCHCRITERIA_NOT_HEADER:
1347                 negative_cond = TRUE;
1348                 break;
1349         }
1350         
1351         switch(prop->criteria) {
1352         case MATCHCRITERIA_ALL:
1353                 break;
1354
1355         case MATCHCRITERIA_NOT_SUBJECT:
1356         case MATCHCRITERIA_NOT_FROM:
1357         case MATCHCRITERIA_NOT_TO:
1358         case MATCHCRITERIA_NOT_CC:
1359         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1360         case MATCHCRITERIA_NOT_NEWSGROUPS:
1361         case MATCHCRITERIA_NOT_INREPLYTO:
1362         case MATCHCRITERIA_NOT_REFERENCES:
1363         case MATCHCRITERIA_NOT_HEADERS_PART:
1364         case MATCHCRITERIA_NOT_BODY_PART:
1365         case MATCHCRITERIA_NOT_MESSAGE:
1366         case MATCHCRITERIA_SUBJECT:
1367         case MATCHCRITERIA_FROM:
1368         case MATCHCRITERIA_TO:
1369         case MATCHCRITERIA_CC:
1370         case MATCHCRITERIA_TO_OR_CC:
1371         case MATCHCRITERIA_NEWSGROUPS:
1372         case MATCHCRITERIA_INREPLYTO:
1373         case MATCHCRITERIA_REFERENCES:
1374         case MATCHCRITERIA_HEADERS_PART:
1375         case MATCHCRITERIA_BODY_PART:
1376         case MATCHCRITERIA_MESSAGE:
1377         case MATCHCRITERIA_EXECUTE:
1378                 gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), prop->expr);
1379                 break;
1380
1381         case MATCHCRITERIA_AGE_GREATER:
1382         case MATCHCRITERIA_AGE_LOWER:
1383         case MATCHCRITERIA_SCORE_GREATER:
1384         case MATCHCRITERIA_SCORE_LOWER:
1385         case MATCHCRITERIA_SCORE_EQUAL:
1386         case MATCHCRITERIA_SIZE_GREATER:
1387         case MATCHCRITERIA_SIZE_SMALLER:
1388         case MATCHCRITERIA_SIZE_EQUAL:
1389                 gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), itos(prop->value));
1390                 break;
1391
1392         case MATCHCRITERIA_NOT_COLORLABEL:
1393         case MATCHCRITERIA_COLORLABEL:
1394                 gtk_option_menu_set_history(GTK_OPTION_MENU(matcher.color_optmenu),
1395                                             prop->value);
1396                 break;
1397
1398         case MATCHCRITERIA_NOT_HEADER:
1399         case MATCHCRITERIA_HEADER:
1400                 gtk_entry_set_text(GTK_ENTRY(matcher.header_entry), prop->header);
1401                 gtk_entry_set_text(GTK_ENTRY(matcher.value_entry), prop->expr);
1402                 break;
1403         }
1404
1405         if (negative_cond)
1406                 gtk_list_select_item(GTK_LIST(matcher.predicate_list), 1);
1407         else
1408                 gtk_list_select_item(GTK_LIST(matcher.predicate_list), 0);
1409
1410         switch(prop->matchtype) {
1411         case MATCHTYPE_MATCH:
1412                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_chkbtn), FALSE);
1413                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_chkbtn), TRUE);
1414                 break;
1415
1416         case MATCHTYPE_MATCHCASE:
1417                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_chkbtn), FALSE);
1418                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_chkbtn), FALSE);
1419                 break;
1420
1421         case MATCHTYPE_REGEXP:
1422                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_chkbtn), TRUE);
1423                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_chkbtn), TRUE);
1424                 break;
1425
1426         case MATCHTYPE_REGEXPCASE:
1427                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.regexp_chkbtn), TRUE);
1428                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(matcher.case_chkbtn), FALSE);
1429                 break;
1430         }
1431 }
1432
1433 /*!
1434  *\brief        Helper function that allows us to replace the 'Value' entry box
1435  *              by another widget.
1436  *
1437  *\param        old_widget Widget that needs to be removed.
1438  *\param        new_widget Replacement widget
1439  */
1440 static void prefs_matcher_set_value_widget(GtkWidget *old_widget, 
1441                                            GtkWidget *new_widget)
1442 {
1443         /* TODO: find out why the following spews harmless "parent errors" */
1444
1445         /* NOTE: we first need to bump up the refcount of the old_widget,
1446          * because the gtkut_container_remove() will otherwise destroy it */
1447         gtk_widget_ref(old_widget);
1448         gtkut_container_remove(GTK_CONTAINER(matcher.criteria_table), old_widget);
1449         gtk_widget_show(new_widget);
1450         gtk_widget_set_usize(new_widget, 200, -1);
1451         gtk_table_attach(GTK_TABLE(matcher.criteria_table), new_widget, 
1452                          2, 3, 1, 2, 
1453                          GTK_FILL | GTK_SHRINK | GTK_EXPAND, 
1454                          0, 0, 0);
1455 }
1456
1457 /*!
1458  *\brief        Change widgets depending on the selected condition
1459  *
1460  *\param        list List widget
1461  *\param        widget Not used
1462  *\param        user_data Not used      
1463  */
1464 static void prefs_matcher_criteria_select(GtkList *list,
1465                                           GtkWidget *widget,
1466                                           gpointer user_data)
1467 {
1468         gint value, old_value;
1469
1470         old_value = matcher.selected_criteria;
1471         matcher.selected_criteria = value = get_sel_from_list
1472                 (GTK_LIST(matcher.criteria_list));
1473         
1474         /* CLAWS: the value widget is currently either the color label combo box,
1475          * or a GtkEntry, so kiss for now */
1476         if (matcher.selected_criteria == CRITERIA_COLORLABEL) { 
1477                 prefs_matcher_set_value_widget(matcher.value_entry, 
1478                                                matcher.color_optmenu);
1479         } else if (old_value == CRITERIA_COLORLABEL) {
1480                 prefs_matcher_set_value_widget(matcher.color_optmenu,
1481                                                matcher.value_entry);
1482         }                                              
1483
1484         switch (value) {
1485         case CRITERIA_ALL:
1486                 gtk_widget_set_sensitive(matcher.header_combo, FALSE);
1487                 gtk_widget_set_sensitive(matcher.header_label, FALSE);
1488                 gtk_widget_set_sensitive(matcher.value_label, FALSE);
1489                 gtk_widget_set_sensitive(matcher.value_entry, FALSE);
1490                 gtk_widget_set_sensitive(matcher.predicate_label, FALSE);
1491                 gtk_widget_set_sensitive(matcher.predicate_combo, FALSE);
1492                 gtk_widget_set_sensitive(matcher.predicate_flag_combo, FALSE);
1493                 gtk_widget_hide(matcher.predicate_combo);
1494                 gtk_widget_show(matcher.predicate_flag_combo);
1495                 gtk_widget_set_sensitive(matcher.case_chkbtn, FALSE);
1496                 gtk_widget_set_sensitive(matcher.regexp_chkbtn, FALSE);
1497                 gtk_widget_set_sensitive(matcher.exec_btn, FALSE);
1498                 break;
1499
1500         case CRITERIA_UNREAD:
1501         case CRITERIA_NEW:
1502         case CRITERIA_MARKED:
1503         case CRITERIA_DELETED:
1504         case CRITERIA_REPLIED:
1505         case CRITERIA_FORWARDED:
1506         case CRITERIA_LOCKED:
1507         case CRITERIA_IGNORE_THREAD:
1508                 gtk_widget_set_sensitive(matcher.header_combo, FALSE);
1509                 gtk_widget_set_sensitive(matcher.header_label, FALSE);
1510                 gtk_widget_set_sensitive(matcher.value_label, FALSE);
1511                 gtk_widget_set_sensitive(matcher.value_entry, FALSE);
1512                 gtk_widget_set_sensitive(matcher.predicate_label, TRUE);
1513                 gtk_widget_set_sensitive(matcher.predicate_combo, FALSE);
1514                 gtk_widget_set_sensitive(matcher.predicate_flag_combo, TRUE);
1515                 gtk_widget_hide(matcher.predicate_combo);
1516                 gtk_widget_show(matcher.predicate_flag_combo);
1517                 gtk_widget_set_sensitive(matcher.case_chkbtn, FALSE);
1518                 gtk_widget_set_sensitive(matcher.regexp_chkbtn, FALSE);
1519                 gtk_widget_set_sensitive(matcher.exec_btn, FALSE);
1520                 break;
1521                 
1522         case CRITERIA_COLORLABEL:
1523                 gtk_widget_set_sensitive(matcher.header_combo, FALSE);
1524                 gtk_widget_set_sensitive(matcher.header_label, FALSE);
1525                 gtk_widget_set_sensitive(matcher.value_label, TRUE);
1526                 gtk_widget_set_sensitive(matcher.predicate_label, TRUE);
1527                 gtk_widget_set_sensitive(matcher.predicate_combo, FALSE);
1528                 gtk_widget_set_sensitive(matcher.predicate_flag_combo, TRUE);
1529                 gtk_widget_hide(matcher.predicate_combo);
1530                 gtk_widget_show(matcher.predicate_flag_combo);
1531                 gtk_widget_set_sensitive(matcher.case_chkbtn, FALSE);
1532                 gtk_widget_set_sensitive(matcher.regexp_chkbtn, FALSE);
1533                 gtk_widget_set_sensitive(matcher.exec_btn, FALSE);
1534                 break;
1535
1536         case CRITERIA_SUBJECT:
1537         case CRITERIA_FROM:
1538         case CRITERIA_TO:
1539         case CRITERIA_CC:
1540         case CRITERIA_TO_OR_CC:
1541         case CRITERIA_NEWSGROUPS:
1542         case CRITERIA_INREPLYTO:
1543         case CRITERIA_REFERENCES:
1544         case CRITERIA_HEADERS_PART:
1545         case CRITERIA_BODY_PART:
1546         case CRITERIA_MESSAGE:
1547                 gtk_widget_set_sensitive(matcher.header_combo, FALSE);
1548                 gtk_widget_set_sensitive(matcher.header_label, FALSE);
1549                 gtk_widget_set_sensitive(matcher.value_label, TRUE);
1550                 gtk_widget_set_sensitive(matcher.value_entry, TRUE);
1551                 gtk_widget_set_sensitive(matcher.predicate_label, TRUE);
1552                 gtk_widget_set_sensitive(matcher.predicate_combo, TRUE);
1553                 gtk_widget_set_sensitive(matcher.predicate_flag_combo, FALSE);
1554                 gtk_widget_show(matcher.predicate_combo);
1555                 gtk_widget_hide(matcher.predicate_flag_combo);
1556                 gtk_widget_set_sensitive(matcher.case_chkbtn, TRUE);
1557                 gtk_widget_set_sensitive(matcher.regexp_chkbtn, TRUE);
1558                 gtk_widget_set_sensitive(matcher.exec_btn, FALSE);
1559                 break;
1560
1561         case CRITERIA_EXECUTE:
1562                 gtk_widget_set_sensitive(matcher.header_combo, FALSE);
1563                 gtk_widget_set_sensitive(matcher.header_label, FALSE);
1564                 gtk_widget_set_sensitive(matcher.value_label, TRUE);
1565                 gtk_widget_set_sensitive(matcher.value_entry, TRUE);
1566                 gtk_widget_set_sensitive(matcher.predicate_label, TRUE);
1567                 gtk_widget_set_sensitive(matcher.predicate_combo, FALSE);
1568                 gtk_widget_set_sensitive(matcher.predicate_flag_combo, TRUE);
1569                 gtk_widget_hide(matcher.predicate_combo);
1570                 gtk_widget_show(matcher.predicate_flag_combo);
1571                 gtk_widget_set_sensitive(matcher.case_chkbtn, FALSE);
1572                 gtk_widget_set_sensitive(matcher.regexp_chkbtn, FALSE);
1573                 gtk_widget_set_sensitive(matcher.exec_btn, TRUE);
1574                 break;
1575
1576         case CRITERIA_AGE_GREATER:
1577         case CRITERIA_AGE_LOWER:
1578         case CRITERIA_SCORE_GREATER:
1579         case CRITERIA_SCORE_LOWER:
1580         case CRITERIA_SCORE_EQUAL:
1581         case CRITERIA_SIZE_GREATER:
1582         case CRITERIA_SIZE_SMALLER:
1583         case CRITERIA_SIZE_EQUAL:
1584                 gtk_widget_set_sensitive(matcher.header_combo, FALSE);
1585                 gtk_widget_set_sensitive(matcher.header_label, FALSE);
1586                 gtk_widget_set_sensitive(matcher.value_label, TRUE);
1587                 gtk_widget_set_sensitive(matcher.value_entry, TRUE);
1588                 gtk_widget_set_sensitive(matcher.predicate_label, FALSE);
1589                 gtk_widget_set_sensitive(matcher.predicate_combo, FALSE);
1590                 gtk_widget_set_sensitive(matcher.predicate_flag_combo, FALSE);
1591                 gtk_widget_show(matcher.predicate_combo);
1592                 gtk_widget_hide(matcher.predicate_flag_combo);
1593                 gtk_widget_set_sensitive(matcher.case_chkbtn, FALSE);
1594                 gtk_widget_set_sensitive(matcher.regexp_chkbtn, FALSE);
1595                 gtk_widget_set_sensitive(matcher.exec_btn, FALSE);
1596                 break;
1597
1598         case CRITERIA_HEADER:
1599                 gtk_widget_set_sensitive(matcher.header_combo, TRUE);
1600                 gtk_widget_set_sensitive(matcher.header_label, TRUE);
1601                 gtk_widget_set_sensitive(matcher.value_label, TRUE);
1602                 gtk_widget_set_sensitive(matcher.value_entry, TRUE);
1603                 gtk_widget_set_sensitive(matcher.predicate_label, TRUE);
1604                 gtk_widget_set_sensitive(matcher.predicate_combo, TRUE);
1605                 gtk_widget_set_sensitive(matcher.predicate_flag_combo, FALSE);
1606                 gtk_widget_show(matcher.predicate_combo);
1607                 gtk_widget_hide(matcher.predicate_flag_combo);
1608                 gtk_widget_set_sensitive(matcher.case_chkbtn, TRUE);
1609                 gtk_widget_set_sensitive(matcher.regexp_chkbtn, TRUE);
1610                 gtk_widget_set_sensitive(matcher.exec_btn, FALSE);
1611                 break;
1612         }
1613 }
1614
1615 /*!
1616  *\brief        Handle key press
1617  *
1618  *\param        widget Widget receiving key press
1619  *\param        event Key event
1620  *\param        data User data
1621  */
1622 static void prefs_matcher_key_pressed(GtkWidget *widget, GdkEventKey *event,
1623                                      gpointer data)
1624 {
1625         if (event && event->keyval == GDK_Escape)
1626                 prefs_matcher_cancel();
1627 }
1628
1629 /*!
1630  *\brief        Cancel matcher dialog
1631  */
1632 static void prefs_matcher_cancel(void)
1633 {
1634         gtk_widget_hide(matcher.window);
1635         inc_unlock();
1636 }
1637
1638 /*!
1639  *\brief        Accept current matchers
1640  */
1641 static void prefs_matcher_ok(void)
1642 {
1643         MatcherList *matchers;
1644         MatcherProp *matcherprop;
1645         AlertValue val;
1646         gchar *matcher_str;
1647         gchar *str;
1648         gint row = 1;
1649
1650         matchers = prefs_matcher_get_list();
1651
1652         if (matchers != NULL) {
1653                 matcherprop = prefs_matcher_dialog_to_matcher();
1654                 if (matcherprop != NULL) {
1655                         str = matcherprop_to_string(matcherprop);
1656                         if(strcmp(str, "all") != 0) {
1657                                 while (gtk_clist_get_text(GTK_CLIST(matcher.cond_clist),
1658                                                   row, 0, &matcher_str)) {
1659                                         if (strcmp(matcher_str, str) == 0) break;
1660                                         row++;
1661                                 }
1662
1663                                 if (strcmp(matcher_str, str) != 0) {
1664                                         val = alertpanel(_("Entry not saved"),
1665                                                  _("The entry was not saved\nHave you really finished?"),
1666                                                  _("Yes"), _("No"), NULL);
1667                                         if (G_ALERTDEFAULT != val) {
1668                                                 g_free(str);
1669                                                 return;
1670                                         }
1671                                 }
1672                         }
1673                 }
1674                 g_free(str);
1675                 gtk_widget_hide(matcher.window);
1676                 inc_unlock();
1677                 if (matchers_callback != NULL)
1678                         matchers_callback(matchers);
1679                 matcherlist_free(matchers);
1680         }
1681 }
1682
1683 /*!
1684  *\brief        Called when closing dialog box
1685  *
1686  *\param        widget Dialog widget
1687  *\param        event Event info
1688  *\param        data User data
1689  *
1690  *\return       gint TRUE
1691  */
1692 static gint prefs_matcher_deleted(GtkWidget *widget, GdkEventAny *event,
1693                                   gpointer data)
1694 {
1695         prefs_matcher_cancel();
1696         return TRUE;
1697 }
1698
1699 /*
1700  * Strings describing exec format strings
1701  * 
1702  * When adding new lines, remember to put 2 strings for each line
1703  */
1704 static gchar *exec_desc_strings[] = {
1705         "%%",   "%",
1706         "%s",   N_("Subject"),
1707         "%f",   N_("From"),
1708         "%t",   N_("To"),
1709         "%c",   N_("Cc"),
1710         "%d",   N_("Date"),
1711         "%i",   N_("Message-ID"),
1712         "%n",   N_("Newsgroups"),
1713         "%r",   N_("References"),
1714         "%F",   N_("Filename - should not be modified"),
1715         "\\n",  N_("new line"),
1716         "\\",   N_("escape character for quotes"),
1717         "\\\"",N_("quote character"),
1718         NULL, NULL
1719 };
1720
1721 static DescriptionWindow exec_desc_win = { 
1722         NULL, 
1723         2,
1724         N_("Description of symbols"),
1725         exec_desc_strings
1726 };
1727
1728
1729
1730 /*!
1731  *\brief        Show Execute action's info
1732  */
1733 void prefs_matcher_exec_info(void)
1734 {
1735         description_window_create(&exec_desc_win);
1736 }
1737