dd9eb983590c542468e8118f88fc98b3e7e88aac
[claws.git] / src / gtk / quicksearch.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2004 Hiroyuki Yamamoto & the Sylpheed-Claws team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <ctype.h>
27
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30
31 #include "utils.h"
32 #include "menu.h"
33 #include "prefs_common.h"
34 #include "description_window.h"
35 #include "matcher.h"
36 #include "matcher_parser.h"
37 #include "quicksearch.h"
38 #include "folderview.h"
39 #include "folder.h"
40 #include "prefs_matcher.h"
41
42 struct _QuickSearch
43 {
44         GtkWidget                       *hbox_search;
45         GtkWidget                       *search_type;
46         GtkWidget                       *search_type_opt;
47         GtkWidget                       *search_string_entry;
48         GtkWidget                       *search_condition_expression;
49         GtkWidget                       *search_description;
50
51         gboolean                         active;
52         gchar                           *search_string;
53         MatcherList                     *matcher_list;
54
55         QuickSearchExecuteCallback       callback;
56         gpointer                         callback_data;
57         gboolean                         running;
58         gboolean                         has_focus;
59         FolderItem                      *root_folder_item;
60 };
61
62 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
63 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
64 static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item);
65
66 static void prepare_matcher(QuickSearch *quicksearch)
67 {
68         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
69
70         if (quicksearch->matcher_list != NULL) {
71                 matcherlist_free(quicksearch->matcher_list);
72                 quicksearch->matcher_list = NULL;
73         }
74
75         if (search_string == NULL || search_string[0] == '\0') {
76                 quicksearch_set_active(quicksearch, FALSE);
77                 return;
78         }
79
80         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
81                 char *newstr = NULL;
82
83                 newstr = expand_search_string(search_string);
84                 if (newstr && newstr[0] != '\0') {
85                         quicksearch->matcher_list = matcher_parser_get_cond(newstr);
86                         g_free(newstr);
87                 } else {
88                         quicksearch->matcher_list = NULL;
89                         quicksearch_set_active(quicksearch, FALSE);
90
91                         return;
92                 }
93         } else {
94                 if (quicksearch->search_string != NULL)
95                         g_free(quicksearch->search_string);
96                 quicksearch->search_string = g_strdup(search_string);
97         }
98
99         quicksearch_set_active(quicksearch, TRUE);
100 }
101
102 static void update_extended_buttons (QuickSearch *quicksearch)
103 {
104         GtkWidget *expr_btn = quicksearch->search_condition_expression;
105         GtkWidget *ext_btn = quicksearch->search_description;
106         
107         g_return_if_fail(expr_btn != NULL);
108         g_return_if_fail(ext_btn != NULL);
109                 
110         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
111                 gtk_widget_show(expr_btn);
112                 gtk_widget_show(ext_btn);
113         } else {
114                 gtk_widget_hide(expr_btn);
115                 gtk_widget_hide(ext_btn);
116         }
117 }
118
119 static gboolean searchbar_focus_evt(GtkWidget *widget, GdkEventFocus *event,
120                                   QuickSearch *quicksearch)
121 {
122         quicksearch->has_focus = (event && event->in);
123         return FALSE;
124 }
125
126 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
127 {
128         return quicksearch->has_focus;
129 }
130
131 static void searchbar_run(QuickSearch *quicksearch)
132 {
133         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
134
135         if (search_string && strlen(search_string) != 0) {
136                 prefs_common.summary_quicksearch_history =
137                         add_history(prefs_common.summary_quicksearch_history,
138                                         search_string);
139                 gtk_combo_set_popdown_strings(GTK_COMBO(quicksearch->search_string_entry), 
140                         prefs_common.summary_quicksearch_history);                      
141         }
142
143         prepare_matcher(quicksearch);
144
145         quicksearch_set_running(quicksearch, TRUE);
146         if (quicksearch->callback != NULL)
147                 quicksearch->callback(quicksearch, quicksearch->callback_data);
148         quicksearch_set_running(quicksearch, FALSE);
149 }
150
151 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
152                                   QuickSearch *quicksearch)
153 {
154         if (event != NULL && event->keyval == GDK_Escape) {
155                 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
156                 return TRUE;
157         }
158
159         if (event != NULL && event->keyval == GDK_Return) {
160                 /* add expression to history list and exec quicksearch */
161                 searchbar_run(quicksearch);
162
163                 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
164                 return TRUE;
165         }
166
167         return FALSE;           
168 }
169
170 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
171 {
172         QuickSearch *quicksearch = (QuickSearch *)data;
173
174         prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
175                                    G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
176                                    GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
177
178         /* Show extended search description button, only when Extended is selected */
179         update_extended_buttons(quicksearch);
180
181         prepare_matcher(quicksearch);
182
183         quicksearch_set_running(quicksearch, TRUE);
184         if (quicksearch->callback != NULL)
185                 quicksearch->callback(quicksearch, quicksearch->callback_data);
186         quicksearch_set_running(quicksearch, FALSE);
187         return TRUE;
188 }
189
190 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
191 {
192         QuickSearch *quicksearch = (QuickSearch *)data;
193         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
194         
195         prefs_common.summary_quicksearch_recurse = checked; 
196
197         /* reselect the search type */
198         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt), 
199                                     prefs_common.summary_quicksearch_type);
200
201         prepare_matcher(quicksearch);
202
203         quicksearch_set_running(quicksearch, TRUE);
204         if (quicksearch->callback != NULL)
205                 quicksearch->callback(quicksearch, quicksearch->callback_data);
206         quicksearch_set_running(quicksearch, FALSE);
207         return TRUE;
208 }
209
210 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
211 {
212         QuickSearch *quicksearch = (QuickSearch *)data;
213         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
214         
215         prefs_common.summary_quicksearch_sticky = checked; 
216
217         /* reselect the search type */
218         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt), 
219                                     prefs_common.summary_quicksearch_type);
220
221         return TRUE;
222 }
223
224 /*
225  * Strings describing how to use Extended Search
226  * 
227  * When adding new lines, remember to put 2 strings for each line
228  */
229 static gchar *search_descr_strings[] = {
230         "a",     N_("all messages"),
231         "ag #",  N_("messages whose age is greater than #"),
232         "al #",  N_("messages whose age is less than #"),
233         "b S",   N_("messages which contain S in the message body"),
234         "B S",   N_("messages which contain S in the whole message"),
235         "c S",   N_("messages carbon-copied to S"),
236         "C S",   N_("message is either to: or cc: to S"),
237         "D",     N_("deleted messages"), /** how I can filter deleted messages **/
238         "e S",   N_("messages which contain S in the Sender field"),
239         "E S",   N_("true if execute \"S\" succeeds"),
240         "f S",   N_("messages originating from user S"),
241         "F",     N_("forwarded messages"),
242         "h S",   N_("messages which contain header S"),
243         "i S",   N_("messages which contain S in Message-ID header"),
244         "I S",   N_("messages which contain S in inreplyto header"),
245         "L",     N_("locked messages"),
246         "n S",   N_("messages which are in newsgroup S"),
247         "N",     N_("new messages"),
248         "O",     N_("old messages"),
249         "p",     N_("incomplete messages (not entirely downloaded)"),
250         "r",     N_("messages which have been replied to"),
251         "R",     N_("read messages"),
252         "s S",   N_("messages which contain S in subject"),
253         "se #",  N_("messages whose score is equal to #"),
254         "sg #",  N_("messages whose score is greater than #"),
255         "sl #",  N_("messages whose score is lower than #"),
256         "Se #",  N_("messages whose size is equal to #"),
257         "Sg #",  N_("messages whose size is greater than #"),
258         "Ss #",  N_("messages whose size is smaller than #"),
259         "t S",   N_("messages which have been sent to S"),
260         "T",     N_("marked messages"),
261         "U",     N_("unread messages"),
262         "x S",   N_("messages which contain S in References header"),
263         "X cmd", N_("messages returning 0 when passed to command"),
264         "y S",   N_("messages which contain S in X-Label header"),
265         "",      "" ,
266         "&",     N_("logical AND operator"),
267         "|",     N_("logical OR operator"),
268         "! or ~",       N_("logical NOT operator"),
269         "%",     N_("case sensitive search"),
270         "",      "" ,
271         " ",     N_("all filtering expressions are allowed"),
272         NULL,    NULL 
273 };
274  
275 static DescriptionWindow search_descr = {
276         NULL,
277         NULL, 
278         2,
279         N_("Extended Search symbols"),
280         search_descr_strings
281 };
282         
283 static void search_description_cb(GtkWidget *widget)
284 {
285         description_window_create(&search_descr);
286 };
287
288 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
289 {
290         QuickSearch *quicksearch = (QuickSearch *)data;
291         
292         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
293         
294         return TRUE;
295 };
296
297 static void search_condition_expr_done(MatcherList * matchers)
298 {
299         gchar *str;
300
301         g_return_if_fail(
302                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
303
304         if (matchers == NULL)
305                 return;
306
307         str = matcherlist_to_string(matchers);
308
309         if (str != NULL) {
310                 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
311                                 prefs_common.summary_quicksearch_type, str);
312                 g_free(str);
313
314                 /* add expression to history list and exec quicksearch */
315                 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch);
316         }
317 }
318
319 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
320 {
321         const gchar * cond_str;
322         MatcherList * matchers = NULL;
323
324         g_return_val_if_fail(
325                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
326                         FALSE);
327
328         /* re-use it the current quicksearch value if it's a condition expression,
329            otherwise ignore it silently */
330         cond_str = gtk_entry_get_text(
331                         GTK_ENTRY(GTK_COMBO(mainwindow_get_mainwindow()->summaryview->quicksearch->
332                         search_string_entry)->entry));
333         if (*cond_str != '\0') {
334                 matchers = matcher_parser_get_cond((gchar*)cond_str);
335         }
336
337         prefs_matcher_open(matchers, search_condition_expr_done);
338
339         if (matchers != NULL)
340                 matcherlist_free(matchers);
341
342         return TRUE;
343 };
344                 
345 QuickSearch *quicksearch_new()
346 {
347         QuickSearch *quicksearch;
348
349         GtkWidget *hbox_search;
350         GtkWidget *search_type_opt;
351         GtkWidget *search_type;
352         GtkWidget *search_string_entry;
353         GtkWidget *search_hbox;
354         GtkWidget *search_description;
355         GtkWidget *clear_search;
356         GtkWidget *search_condition_expression;
357         GtkWidget *menuitem;
358         GtkTooltips *search_cond_expr_tip;
359
360         quicksearch = g_new0(QuickSearch, 1);
361
362         /* quick search */
363         hbox_search = gtk_hbox_new(FALSE, 0);
364
365         search_type_opt = gtk_option_menu_new();
366         gtk_widget_show(search_type_opt);
367         gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
368
369         search_type = gtk_menu_new();
370         MENUITEM_ADD (search_type, menuitem, _("Subject"), QUICK_SEARCH_SUBJECT);
371         g_signal_connect(G_OBJECT(menuitem), "activate",
372                          G_CALLBACK(searchtype_changed),
373                          quicksearch);
374         MENUITEM_ADD (search_type, menuitem, _("From"), QUICK_SEARCH_FROM);
375         g_signal_connect(G_OBJECT(menuitem), "activate",
376                          G_CALLBACK(searchtype_changed),
377                          quicksearch);
378         MENUITEM_ADD (search_type, menuitem, _("To"), QUICK_SEARCH_TO);
379         g_signal_connect(G_OBJECT(menuitem), "activate",
380                          G_CALLBACK(searchtype_changed),
381                          quicksearch);
382         MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED);
383         g_signal_connect(G_OBJECT(menuitem), "activate",
384                          G_CALLBACK(searchtype_changed),
385                          quicksearch);
386
387         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
388         
389         menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
390         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
391         
392         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
393                                         prefs_common.summary_quicksearch_recurse);
394         
395         g_signal_connect(G_OBJECT(menuitem), "activate",
396                          G_CALLBACK(searchtype_recursive_changed),
397                          quicksearch);
398
399         menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
400         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
401         
402         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
403                                         prefs_common.summary_quicksearch_sticky);
404         
405         g_signal_connect(G_OBJECT(menuitem), "activate",
406                          G_CALLBACK(searchtype_sticky_changed),
407                          quicksearch);
408
409         gtk_option_menu_set_menu(GTK_OPTION_MENU(search_type_opt), search_type);
410         
411         gtk_option_menu_set_history(GTK_OPTION_MENU(search_type_opt), prefs_common.summary_quicksearch_type);
412         
413         gtk_widget_show(search_type);
414         
415         search_string_entry = gtk_combo_new();
416         gtk_box_pack_start(GTK_BOX(hbox_search), search_string_entry, FALSE, FALSE, 2);
417         gtk_combo_set_value_in_list(GTK_COMBO(search_string_entry), FALSE, TRUE);
418         gtk_combo_set_case_sensitive(GTK_COMBO(search_string_entry), TRUE);
419         if (prefs_common.summary_quicksearch_history) 
420                 gtk_combo_set_popdown_strings(GTK_COMBO(search_string_entry), 
421                         prefs_common.summary_quicksearch_history);
422         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(search_string_entry)->entry), "");
423         gtk_widget_show(search_string_entry);
424
425         search_hbox = gtk_hbox_new(FALSE, 5);
426
427         clear_search = gtk_button_new_with_label(_(" Clear "));
428         gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
429                            FALSE, FALSE, 0);
430         g_signal_connect(G_OBJECT(clear_search), "clicked",
431                          G_CALLBACK(clear_search_cb), quicksearch);
432         gtk_widget_show(clear_search);
433
434         search_condition_expression = gtk_button_new_with_label (_(" ... "));
435         gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
436                            FALSE, FALSE, 0);
437         g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
438                          G_CALLBACK(search_condition_expr),
439                          quicksearch);
440         search_cond_expr_tip = gtk_tooltips_new();
441         gtk_tooltips_set_tip(GTK_TOOLTIPS(search_cond_expr_tip),
442                              search_condition_expression,
443                              _("Quick search: edit filtering condition"), NULL);
444         gtk_widget_show(search_condition_expression);
445
446         search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
447         gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
448                            FALSE, FALSE, 0);
449         g_signal_connect(G_OBJECT(search_description), "clicked",
450                          G_CALLBACK(search_description_cb), NULL);
451         gtk_widget_show(search_description);
452
453         gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);                         
454         gtk_widget_show(search_hbox);
455         
456         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry), 
457                            "key_press_event",
458                            G_CALLBACK(searchbar_pressed),
459                            quicksearch);
460         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
461                          "focus_in_event",
462                          G_CALLBACK(searchbar_focus_evt),
463                          quicksearch);
464         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
465                          "focus_out_event",
466                          G_CALLBACK(searchbar_focus_evt),
467                          quicksearch);
468
469         quicksearch->hbox_search = hbox_search;
470         quicksearch->search_type = search_type;
471         quicksearch->search_type_opt = search_type_opt;
472         quicksearch->search_string_entry = search_string_entry;
473         quicksearch->search_condition_expression = search_condition_expression;
474         quicksearch->search_description = search_description;
475         quicksearch->matcher_list = NULL;
476         quicksearch->active = FALSE;
477         quicksearch->running = FALSE;
478         
479         update_extended_buttons(quicksearch);
480         
481         return quicksearch;
482 }
483
484 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
485 {
486         return quicksearch->hbox_search;
487 }
488
489 void quicksearch_show(QuickSearch *quicksearch)
490 {
491         prepare_matcher(quicksearch);
492         gtk_widget_show(quicksearch->hbox_search);
493         update_extended_buttons(quicksearch);
494         gtk_widget_grab_focus(
495                 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
496 }
497
498 void quicksearch_hide(QuickSearch *quicksearch)
499 {
500         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
501         quicksearch_set_active(quicksearch, FALSE);
502         gtk_widget_hide(quicksearch->hbox_search);
503 }
504
505 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
506                      const gchar *matchstring)
507 {
508         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
509                                     type);
510         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry),
511                            matchstring);
512         prefs_common.summary_quicksearch_type = type;
513
514         prepare_matcher(quicksearch);
515
516         quicksearch_set_running(quicksearch, TRUE);
517         if (quicksearch->callback != NULL)
518                 quicksearch->callback(quicksearch, quicksearch->callback_data); 
519         quicksearch_set_running(quicksearch, FALSE);
520 }
521
522 gboolean quicksearch_is_active(QuickSearch *quicksearch)
523 {
524         return quicksearch->active;
525 }
526
527 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
528 {
529         quicksearch->active = active;
530         if (!active) {
531                 quicksearch_reset_cur_folder_item(quicksearch);
532         }
533 }
534
535 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
536                                       QuickSearchExecuteCallback callback,
537                                       gpointer data)
538 {
539         quicksearch->callback = callback;
540         quicksearch->callback_data = data;
541 }
542
543 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
544 {
545         gchar *searched_header = NULL;
546
547         if (!quicksearch->active)
548                 return TRUE;
549
550         switch (prefs_common.summary_quicksearch_type) {
551         case QUICK_SEARCH_SUBJECT:
552                 searched_header = msginfo->subject;
553                 break;
554         case QUICK_SEARCH_FROM:
555                 searched_header = msginfo->from;
556                 break;
557         case QUICK_SEARCH_TO:
558                 searched_header = msginfo->to;
559                 break;
560         case QUICK_SEARCH_EXTENDED:
561                 break;
562         default:
563                 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
564                 break;
565         }
566
567         if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED && 
568             quicksearch->search_string &&
569             searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
570                 return TRUE;
571         else if ((quicksearch->matcher_list != NULL) && 
572                  matcherlist_match(quicksearch->matcher_list, msginfo))
573                 return TRUE;
574
575         return FALSE;
576 }
577
578 /* allow Mutt-like patterns in quick search */
579 gchar *expand_search_string(const gchar *search_string)
580 {
581         int i = 0;
582         gchar term_char, save_char;
583         gchar *cmd_start, *cmd_end;
584         GString *matcherstr;
585         gchar *returnstr = NULL;
586         gchar *copy_str;
587         gboolean casesens, dontmatch;
588         /* list of allowed pattern abbreviations */
589         struct {
590                 gchar           *abbreviated;   /* abbreviation */
591                 gchar           *command;       /* actual matcher command */ 
592                 gint            numparams;      /* number of params for cmd */
593                 gboolean        qualifier;      /* do we append regexpcase */
594                 gboolean        quotes;         /* do we need quotes */
595         }
596         cmds[] = {
597                 { "a",  "all",                          0,      FALSE,  FALSE },
598                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
599                 { "al", "age_lower",                    1,      FALSE,  FALSE },
600                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
601                 { "B",  "message",                      1,      TRUE,   TRUE  },
602                 { "c",  "cc",                           1,      TRUE,   TRUE  },
603                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
604                 { "D",  "deleted",                      0,      FALSE,  FALSE },
605                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
606                 { "E",  "execute",                      1,      FALSE,  TRUE  },
607                 { "f",  "from",                         1,      TRUE,   TRUE  },
608                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
609                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
610                 { "i",  "header \"Message-ID\"",        1,      TRUE,   TRUE  },
611                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
612                 { "L",  "locked",                       0,      FALSE,  FALSE },
613                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
614                 { "N",  "new",                          0,      FALSE,  FALSE },
615                 { "O",  "~new",                         0,      FALSE,  FALSE },
616                 { "r",  "replied",                      0,      FALSE,  FALSE },
617                 { "R",  "~unread",                      0,      FALSE,  FALSE },
618                 { "s",  "subject",                      1,      TRUE,   TRUE  },
619                 { "se", "score_equal",                  1,      FALSE,  FALSE },
620                 { "sg", "score_greater",                1,      FALSE,  FALSE },
621                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
622                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
623                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
624                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
625                 { "t",  "to",                           1,      TRUE,   TRUE  },
626                 { "T",  "marked",                       0,      FALSE,  FALSE },
627                 { "U",  "unread",                       0,      FALSE,  FALSE },
628                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
629                 { "X",  "test",                         1,      FALSE,  FALSE }, 
630                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
631                 { "&",  "&",                            0,      FALSE,  FALSE },
632                 { "|",  "|",                            0,      FALSE,  FALSE },
633                 { "p",  "partial",                      0,      FALSE,  FALSE },
634                 { NULL, NULL,                           0,      FALSE,  FALSE }
635         };
636
637         if (search_string == NULL)
638                 return NULL;
639
640         copy_str = g_strdup(search_string);
641
642         matcherstr = g_string_sized_new(16);
643         cmd_start = copy_str;
644         while (cmd_start && *cmd_start) {
645                 /* skip all white spaces */
646                 while (*cmd_start && isspace((guchar)*cmd_start))
647                         cmd_start++;
648                 cmd_end = cmd_start;
649
650                 /* extract a command */
651                 while (*cmd_end && !isspace((guchar)*cmd_end))
652                         cmd_end++;
653
654                 /* save character */
655                 save_char = *cmd_end;
656                 *cmd_end = '\0';
657
658                 dontmatch = FALSE;
659                 casesens = FALSE;
660
661                 /* ~ and ! mean logical NOT */
662                 if (*cmd_start == '~' || *cmd_start == '!')
663                 {
664                         dontmatch = TRUE;
665                         cmd_start++;
666                 }
667                 /* % means case sensitive match */
668                 if (*cmd_start == '%')
669                 {
670                         casesens = TRUE;
671                         cmd_start++;
672                 }
673
674                 /* find matching abbreviation */
675                 for (i = 0; cmds[i].command; i++) {
676                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
677                                 /* restore character */
678                                 *cmd_end = save_char;
679
680                                 /* copy command */
681                                 if (matcherstr->len > 0) {
682                                         g_string_append(matcherstr, " ");
683                                 }
684                                 if (dontmatch)
685                                         g_string_append(matcherstr, "~");
686                                 g_string_append(matcherstr, cmds[i].command);
687                                 g_string_append(matcherstr, " ");
688
689                                 /* stop if no params required */
690                                 if (cmds[i].numparams == 0)
691                                         break;
692
693                                 /* extract a parameter, allow quotes */
694                                 while (*cmd_end && isspace((guchar)*cmd_end))
695                                         cmd_end++;
696
697                                 cmd_start = cmd_end;
698                                 if (*cmd_start == '"') {
699                                         term_char = '"';
700                                         cmd_end++;
701                                 }
702                                 else
703                                         term_char = ' ';
704
705                                 /* extract actual parameter */
706                                 while ((*cmd_end) && (*cmd_end != term_char))
707                                         cmd_end++;
708
709                                 if (*cmd_end == '"')
710                                         cmd_end++;
711
712                                 save_char = *cmd_end;
713                                 *cmd_end = '\0';
714
715                                 if (cmds[i].qualifier) {
716                                         if (casesens)
717                                                 g_string_append(matcherstr, "regexp ");
718                                         else
719                                                 g_string_append(matcherstr, "regexpcase ");
720                                 }
721
722                                 /* do we need to add quotes ? */
723                                 if (cmds[i].quotes && term_char != '"')
724                                         g_string_append(matcherstr, "\"");
725
726                                 /* copy actual parameter */
727                                 g_string_append(matcherstr, cmd_start);
728
729                                 /* do we need to add quotes ? */
730                                 if (cmds[i].quotes && term_char != '"')
731                                         g_string_append(matcherstr, "\"");
732
733                                 /* restore original character */
734                                 *cmd_end = save_char;
735
736                                 break;
737                         }
738                 }
739
740                 if (*cmd_end)
741                         cmd_end++;
742                 cmd_start = cmd_end;
743         }
744
745         g_free(copy_str);
746
747         /* return search string if no match is found to allow 
748            all available filtering expressions in quicksearch */
749         if (matcherstr->len > 0) returnstr = matcherstr->str;
750         else returnstr = g_strdup(search_string);
751
752         g_string_free(matcherstr, FALSE);
753         return returnstr;
754 }
755
756 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
757 {
758         quicksearch->running = run;
759 }
760
761 gboolean quicksearch_is_running(QuickSearch *quicksearch) 
762 {
763         return quicksearch->running;
764 }
765
766 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
767 {
768         GtkEntry *entry = GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry);
769         gint curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
770         char *str = g_strdup(gtk_entry_get_text(entry));
771         char *begin = str;
772         char *end = NULL;
773         char *new = NULL;
774         
775         if (mod == GDK_SHIFT_MASK)
776                 val = toupper(val);
777         
778         if (curpos < strlen(str)-1) {
779                 end = g_strdup(str+curpos);
780                 *(str+curpos) = '\0';
781                 new = g_strdup_printf("%s%c%s", begin, val, end);
782                 gtk_entry_set_text(entry, new);
783                 g_free(end);
784         } else {
785                 new = g_strdup_printf("%s%c", begin, val);
786                 gtk_entry_set_text(entry, new);
787         }
788         g_free(str);
789         g_free(new);
790         gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
791         
792 }
793
794 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch, 
795                                  FolderItem *src)
796 {
797         GSList *msglist = folder_item_get_msg_list(src);
798         GSList *cur;
799         gboolean result = FALSE;
800         
801         for (cur = msglist; cur != NULL; cur = cur->next) {
802                 MsgInfo *msg = (MsgInfo *)cur->data;
803                 if (quicksearch_match(quicksearch, msg)) {
804                         procmsg_msginfo_free(msg);
805                         result = TRUE;
806                         break;
807                 }
808                 procmsg_msginfo_free(msg);
809         }
810
811         g_slist_free(msglist);
812         return result;
813 }
814
815 void quicksearch_search_subfolders(QuickSearch *quicksearch, 
816                                    FolderView *folderview,
817                                    FolderItem *folder_item)
818 {
819         FolderItem *cur = NULL;
820         GNode *node = folder_item->node->children;
821         
822         if (!prefs_common.summary_quicksearch_recurse)
823                 return;
824
825         for (; node != NULL; node = node->next) {
826                 cur = FOLDER_ITEM(node->data);
827                 if (quicksearch_match_subfolder(quicksearch, cur)) {
828                         folderview_update_search_icon(cur, TRUE);
829                 } else {
830                         folderview_update_search_icon(cur, FALSE);
831                 }
832                 if (cur->node->children)
833                         quicksearch_search_subfolders(quicksearch,
834                                                       folderview,
835                                                       cur);
836         }
837         quicksearch->root_folder_item = folder_item;
838 }
839
840 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
841                                     FolderItem *folder_item)
842 {
843         FolderItem *cur = NULL;
844         GNode *node = (folder_item && folder_item->node) ? 
845                         folder_item->node->children : NULL;
846         
847         for (; node != NULL; node = node->next) {
848                 cur = FOLDER_ITEM(node->data);
849                 folderview_update_search_icon(cur, FALSE);
850                 if (cur->node->children)
851                         quicksearch_reset_folder_items(quicksearch,
852                                                        cur);
853         }
854 }
855
856 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
857 {
858         if (quicksearch->root_folder_item)
859                 quicksearch_reset_folder_items(quicksearch, 
860                                                quicksearch->root_folder_item);
861         
862         quicksearch->root_folder_item = NULL;
863 }