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