07877f576a3a15f3f190584af7246157d77d86c1
[claws.git] / src / gtk / quicksearch.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Colin Leroy <colin@colino.net> 
4  * and the Claws Mail team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <ctype.h>
28
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31
32 #include "utils.h"
33 #include "menu.h"
34 #include "prefs_common.h"
35 #include "description_window.h"
36 #include "matcher.h"
37 #include "matcher_parser.h"
38 #include "quicksearch.h"
39 #include "folderview.h"
40 #include "folder.h"
41 #include "prefs_matcher.h"
42 #include "claws.h"
43 #include "statusbar.h"
44
45 struct _QuickSearch
46 {
47         GtkWidget                       *hbox_search;
48         GtkWidget                       *search_type;
49         GtkWidget                       *search_type_opt;
50         GtkWidget                       *search_string_entry;
51         GtkWidget                       *search_condition_expression;
52         GtkWidget                       *search_description;
53         GtkWidget                       *clear_search;
54
55         gboolean                         active;
56         gchar                           *search_string;
57         MatcherList                     *matcher_list;
58
59         QuickSearchExecuteCallback       callback;
60         gpointer                         callback_data;
61         gboolean                         running;
62         gboolean                         has_focus;
63         gboolean                         matching;
64         gboolean                         deferred_free;
65         FolderItem                      *root_folder_item;
66         gboolean                         is_fast;
67         gboolean                         in_typing;
68         guint                            press_timeout_id;
69
70         /* dynamic and autorun qs settings are exclusive*/
71         GtkWidget                        *dynamic_menuitem;
72         GtkWidget                        *autorun_menuitem;
73 };
74
75 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
76 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
77 static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item);
78 static gchar *expand_search_string(const gchar *str);
79 static gchar *expand_tag_search_string(const gchar *str);
80
81 gboolean quicksearch_is_fast(QuickSearch *quicksearch)
82 {
83         return quicksearch->is_fast;
84 }
85
86 static void quicksearch_set_type(QuickSearch *quicksearch, gint type)
87 {
88         gint index;
89         index = menu_find_option_menu_index(GTK_OPTION_MENU(quicksearch->search_type_opt), 
90                                         GINT_TO_POINTER(type),
91                                         NULL);
92         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt), index);      
93 }
94
95 static void prepare_matcher(QuickSearch *quicksearch)
96 {
97         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
98
99         if (search_string == NULL || search_string[0] == '\0') {
100                 quicksearch_set_active(quicksearch, FALSE);
101         }
102
103         if (quicksearch->matcher_list != NULL) {
104                 if (quicksearch->matching) {
105                         quicksearch->deferred_free = TRUE;
106                         return;
107                 }
108                 quicksearch->deferred_free = FALSE;
109                 matcherlist_free(quicksearch->matcher_list);
110                 quicksearch->matcher_list = NULL;
111         }
112
113         if (search_string == NULL || search_string[0] == '\0') {
114                 return;
115         }
116
117         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
118                 char *newstr = NULL;
119
120                 newstr = expand_search_string(search_string);
121                 if (newstr && newstr[0] != '\0') {
122                         quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
123                         g_free(newstr);
124                 } else {
125                         quicksearch->matcher_list = NULL;
126                         quicksearch_set_active(quicksearch, FALSE);
127
128                         return;
129                 }
130         } else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_TAG) {
131                 char *newstr = expand_tag_search_string(search_string);
132                 quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
133                 g_free(newstr);
134         } else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_MIXED) {
135                 char *newstr = expand_tag_search_string(search_string);
136                 quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
137                 g_free(newstr);
138                 g_free(quicksearch->search_string);
139                 quicksearch->search_string = g_strdup(search_string);
140         } else {
141                 quicksearch->is_fast = TRUE;
142                 g_free(quicksearch->search_string);
143                 quicksearch->search_string = g_strdup(search_string);
144         }
145
146         quicksearch_set_active(quicksearch, TRUE);
147 }
148
149 static void update_extended_buttons (QuickSearch *quicksearch)
150 {
151         GtkWidget *expr_btn = quicksearch->search_condition_expression;
152         GtkWidget *ext_btn = quicksearch->search_description;
153
154         g_return_if_fail(expr_btn != NULL);
155         g_return_if_fail(ext_btn != NULL);
156
157         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
158                 gtk_widget_show(expr_btn);
159                 gtk_widget_show(ext_btn);
160         } else {
161                 gtk_widget_hide(expr_btn);
162                 gtk_widget_hide(ext_btn);
163         }
164 }
165
166 static gboolean searchbar_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
167                                   QuickSearch *qs)
168 {
169         qs->has_focus = TRUE;
170         return FALSE;
171 }
172
173 static gboolean searchbar_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
174                                   QuickSearch *qs)
175 {
176         qs->has_focus = FALSE;
177         qs->in_typing = FALSE;
178         return FALSE;
179 }
180
181 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
182 {
183         return quicksearch->has_focus;
184 }
185
186 static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast)
187 {
188         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
189
190         /* add to history */
191         if (!quicksearch->in_typing && search_string && strlen(search_string) != 0) {
192                 prefs_common.summary_quicksearch_history =
193                         add_history(prefs_common.summary_quicksearch_history,
194                                         search_string);
195                 gtk_combo_set_popdown_strings(GTK_COMBO(quicksearch->search_string_entry),
196                         prefs_common.summary_quicksearch_history);
197         }
198
199         prepare_matcher(quicksearch);
200         if (run_only_if_fast && !quicksearch->is_fast)
201                 return;
202         if (quicksearch->matcher_list == NULL && 
203             prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED &&
204             search_string && strlen(search_string) != 0)
205                 return;
206         quicksearch_set_running(quicksearch, TRUE);
207         if (quicksearch->callback != NULL)
208                 quicksearch->callback(quicksearch, quicksearch->callback_data);
209         quicksearch_set_running(quicksearch, FALSE);
210 }
211
212 static int searchbar_changed_timeout(void *data)
213 {
214         QuickSearch *qs = (QuickSearch *)data;
215         if (qs && prefs_common.summary_quicksearch_dynamic) {
216                 qs->in_typing = TRUE;
217                 searchbar_run(qs, TRUE);
218         }
219         return FALSE;
220 }
221
222 static gboolean searchbar_changed_cb(GtkWidget *widget, QuickSearch *qs)
223 {
224         if (!qs->has_focus && prefs_common.summary_quicksearch_autorun) {
225                 gtk_widget_grab_focus(qs->search_string_entry);
226                 searchbar_run(qs, TRUE);
227                 return FALSE;
228         }
229
230         if (prefs_common.summary_quicksearch_dynamic) {
231                 if (qs->press_timeout_id != -1) {
232                         g_source_remove(qs->press_timeout_id);
233                 }
234                 qs->press_timeout_id = g_timeout_add(500,
235                                 searchbar_changed_timeout, qs);
236         }
237
238         return FALSE;
239 }
240
241 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
242                                   QuickSearch *quicksearch)
243 {
244         if (event != NULL && event->keyval == GDK_Escape) {
245
246                 const gchar *str;
247
248                 quicksearch->in_typing = FALSE;
249
250                 str = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
251                 g_return_val_if_fail(str != NULL, TRUE);
252
253                 /* If the string entry is empty -> hide quicksearch bar. If not -> empty it */
254                 if (!*str) {
255                         summaryview_activate_quicksearch(
256                                 mainwindow_get_mainwindow()->summaryview, 
257                                 FALSE);
258                 } else {
259                         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
260                         gtk_widget_grab_focus(
261                                         mainwindow_get_mainwindow()->summaryview->ctree);
262                 }
263
264                 return TRUE;
265         }
266
267         if (event != NULL && event->keyval == GDK_Return) {
268                 if (quicksearch->press_timeout_id != -1) {
269                         g_source_remove(quicksearch->press_timeout_id);
270                         quicksearch->press_timeout_id = -1;
271                 }
272                 quicksearch->in_typing = FALSE;
273                 /* add expression to history list and exec quicksearch */
274                 searchbar_run(quicksearch, FALSE);
275
276                 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
277                 return TRUE;
278         }
279
280         return FALSE;
281 }
282
283 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
284 {
285         QuickSearch *quicksearch = (QuickSearch *)data;
286         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
287
288         prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
289                                    G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
290                                    GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
291
292         /* Show extended search description button, only when Extended is selected */
293         update_extended_buttons(quicksearch);
294
295         if (!search_string || strlen(search_string) == 0) {
296                 return TRUE;
297         }
298
299         prepare_matcher(quicksearch);
300
301         quicksearch_set_running(quicksearch, TRUE);
302         if (quicksearch->callback != NULL)
303                 quicksearch->callback(quicksearch, quicksearch->callback_data);
304         quicksearch_set_running(quicksearch, FALSE);
305         return TRUE;
306 }
307
308 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
309 {
310         QuickSearch *quicksearch = (QuickSearch *)data;
311         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
312         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
313
314         prefs_common.summary_quicksearch_recurse = checked;
315
316         /* reselect the search type */
317         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
318
319         if (!search_string || strlen(search_string) == 0) {
320                 return TRUE;
321         }
322
323         prepare_matcher(quicksearch);
324
325         quicksearch_set_running(quicksearch, TRUE);
326         if (quicksearch->callback != NULL)
327                 quicksearch->callback(quicksearch, quicksearch->callback_data);
328         quicksearch_set_running(quicksearch, FALSE);
329         return TRUE;
330 }
331
332 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
333 {
334         QuickSearch *quicksearch = (QuickSearch *)data;
335         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
336
337         prefs_common.summary_quicksearch_sticky = checked;
338
339         /* reselect the search type */
340         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
341
342         return TRUE;
343 }
344
345 static gboolean searchtype_dynamic_changed(GtkMenuItem *widget, gpointer data)
346 {
347         QuickSearch *quicksearch = (QuickSearch *)data;
348         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
349
350         prefs_common.summary_quicksearch_dynamic = checked;
351         if (checked)
352                 gtk_check_menu_item_set_active(
353                                 GTK_CHECK_MENU_ITEM(quicksearch->autorun_menuitem),
354                                 FALSE);
355
356         /* reselect the search type */
357         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
358
359         return TRUE;
360 }
361
362 static gboolean searchtype_autorun_changed(GtkMenuItem *widget, gpointer data)
363 {
364         QuickSearch *quicksearch = (QuickSearch *)data;
365         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
366
367         prefs_common.summary_quicksearch_autorun = checked;
368         if (checked)
369                 gtk_check_menu_item_set_active(
370                                 GTK_CHECK_MENU_ITEM(quicksearch->dynamic_menuitem),
371                                 FALSE);
372
373         /* reselect the search type */
374         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
375
376         return TRUE;
377 }
378
379 /*
380  * Strings describing how to use Extended Search
381  *
382  * When adding new lines, remember to put 2 strings for each line
383  */
384 static gchar *search_descr_strings[] = {
385         "a",     N_("all messages"),
386         "ag #",  N_("messages whose age is greater than #"),
387         "al #",  N_("messages whose age is less than #"),
388         "b S",   N_("messages which contain S in the message body"),
389         "B S",   N_("messages which contain S in the whole message"),
390         "c S",   N_("messages carbon-copied to S"),
391         "C S",   N_("message is either to: or cc: to S"),
392         "D",     N_("deleted messages"), /** how I can filter deleted messages **/
393         "e S",   N_("messages which contain S in the Sender field"),
394         "E S",   N_("true if execute \"S\" succeeds"),
395         "f S",   N_("messages originating from user S"),
396         "F",     N_("forwarded messages"),
397         "h S",   N_("messages which contain header S"),
398         "i S",   N_("messages which contain S in Message-ID header"),
399         "I S",   N_("messages which contain S in inreplyto header"),
400         "k #",   N_("messages which are marked with color #"),
401         "L",     N_("locked messages"),
402         "n S",   N_("messages which are in newsgroup S"),
403         "N",     N_("new messages"),
404         "O",     N_("old messages"),
405         "p",     N_("incomplete messages (not entirely downloaded)"),
406         "r",     N_("messages which have been replied to"),
407         "R",     N_("read messages"),
408         "s S",   N_("messages which contain S in subject"),
409         "se #",  N_("messages whose score is equal to #"),
410         "sg #",  N_("messages whose score is greater than #"),
411         "sl #",  N_("messages whose score is lower than #"),
412         "Se #",  N_("messages whose size is equal to #"),
413         "Sg #",  N_("messages whose size is greater than #"),
414         "Ss #",  N_("messages whose size is smaller than #"),
415         "t S",   N_("messages which have been sent to S"),
416         "tg S",  N_("messages which tags contain S"),
417         "tagged",N_("messages which have tag(s)"),
418         "T",     N_("marked messages"),
419         "U",     N_("unread messages"),
420         "x S",   N_("messages which contain S in References header"),
421         "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
422         "y S",   N_("messages which contain S in X-Label header"),
423         "",      "" ,
424         "&amp;",         N_("logical AND operator"),
425         "|",     N_("logical OR operator"),
426         "! or ~",       N_("logical NOT operator"),
427         "%",     N_("case sensitive search"),
428         "",      "" ,
429         " ",     N_("all filtering expressions are allowed"),
430         NULL,    NULL
431 };
432
433 static DescriptionWindow search_descr = {
434         NULL,
435         NULL,
436         2,
437         N_("Extended Search"),
438         N_("Extended Search allows the user to define criteria that messages must "
439            "have in order to match and be displayed in the message list.\n\n"
440            "The following symbols can be used:"),
441         search_descr_strings
442 };
443
444 static void search_description_cb(GtkWidget *widget)
445 {
446         description_window_create(&search_descr);
447 };
448
449 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
450 {
451         QuickSearch *quicksearch = (QuickSearch *)data;
452
453         if (!quicksearch->active)
454                 return TRUE;
455
456         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
457
458         return TRUE;
459 };
460
461 static void search_condition_expr_done(MatcherList * matchers)
462 {
463         gchar *str;
464
465         g_return_if_fail(
466                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
467
468         if (matchers == NULL)
469                 return;
470
471         str = matcherlist_to_string(matchers);
472
473         if (str != NULL) {
474                 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
475                                 prefs_common.summary_quicksearch_type, str);
476                 g_free(str);
477
478                 /* add expression to history list and exec quicksearch */
479                 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch, FALSE);
480         }
481 }
482
483 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
484 {
485         const gchar * cond_str;
486         MatcherList * matchers = NULL;
487         
488         g_return_val_if_fail(
489                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
490                         FALSE);
491
492         /* re-use it the current quicksearch value if it's a condition expression,
493            otherwise ignore it silently */
494         cond_str = gtk_entry_get_text(
495                         GTK_ENTRY(GTK_COMBO(mainwindow_get_mainwindow()->summaryview->quicksearch->
496                         search_string_entry)->entry));
497         if (*cond_str != '\0') {
498                 matchers = matcher_parser_get_cond((gchar*)cond_str, NULL);
499         }
500
501         prefs_matcher_open(matchers, search_condition_expr_done);
502
503         if (matchers != NULL)
504                 matcherlist_free(matchers);
505
506         return TRUE;
507 };
508
509 QuickSearch *quicksearch_new()
510 {
511         QuickSearch *quicksearch;
512
513         GtkWidget *hbox_search;
514         GtkWidget *search_type_opt;
515         GtkWidget *search_type;
516         GtkWidget *search_string_entry;
517         GtkWidget *search_hbox;
518         GtkWidget *search_description;
519         GtkWidget *clear_search;
520         GtkWidget *search_condition_expression;
521         GtkWidget *menuitem;
522         GtkTooltips *tips = gtk_tooltips_new();
523
524         quicksearch = g_new0(QuickSearch, 1);
525
526         /* quick search */
527         hbox_search = gtk_hbox_new(FALSE, 0);
528
529         search_type_opt = gtk_option_menu_new();
530         gtk_widget_show(search_type_opt);
531         gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
532
533         search_type = gtk_menu_new();
534         MENUITEM_ADD (search_type, menuitem,
535                         prefs_common_translated_header_name("Subject"), QUICK_SEARCH_SUBJECT);
536         g_signal_connect(G_OBJECT(menuitem), "activate",
537                          G_CALLBACK(searchtype_changed),
538                          quicksearch);
539         MENUITEM_ADD (search_type, menuitem,
540                         prefs_common_translated_header_name("From"), QUICK_SEARCH_FROM);
541         g_signal_connect(G_OBJECT(menuitem), "activate",
542                          G_CALLBACK(searchtype_changed),
543                          quicksearch);
544         MENUITEM_ADD (search_type, menuitem,
545                         prefs_common_translated_header_name("To"), QUICK_SEARCH_TO);
546         g_signal_connect(G_OBJECT(menuitem), "activate",
547                          G_CALLBACK(searchtype_changed),
548                          quicksearch);
549         MENUITEM_ADD (search_type, menuitem,
550                         prefs_common_translated_header_name("Tag"), QUICK_SEARCH_TAG);
551         g_signal_connect(G_OBJECT(menuitem), "activate",
552                          G_CALLBACK(searchtype_changed),
553                          quicksearch);
554         MENUITEM_ADD (search_type, menuitem,
555                         _("From/To/Subject/Tag"), QUICK_SEARCH_MIXED);
556         g_signal_connect(G_OBJECT(menuitem), "activate",
557                          G_CALLBACK(searchtype_changed),
558                          quicksearch);
559         MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED);
560         g_signal_connect(G_OBJECT(menuitem), "activate",
561                          G_CALLBACK(searchtype_changed),
562                          quicksearch);
563
564         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
565
566         menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
567         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
568
569         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
570                                         prefs_common.summary_quicksearch_recurse);
571
572         g_signal_connect(G_OBJECT(menuitem), "activate",
573                          G_CALLBACK(searchtype_recursive_changed),
574                          quicksearch);
575
576         menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
577         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
578
579         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
580                                         prefs_common.summary_quicksearch_sticky);
581
582         g_signal_connect(G_OBJECT(menuitem), "activate",
583                          G_CALLBACK(searchtype_sticky_changed),
584                          quicksearch);
585
586         menuitem = gtk_check_menu_item_new_with_label(_("Type-ahead"));
587         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
588
589         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
590                                         prefs_common.summary_quicksearch_dynamic);
591
592         quicksearch->dynamic_menuitem = menuitem;
593
594         g_signal_connect(G_OBJECT(menuitem), "activate",
595                          G_CALLBACK(searchtype_dynamic_changed),
596                          quicksearch);
597
598         menuitem = gtk_check_menu_item_new_with_label(_("Run on select"));
599         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
600
601         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
602                                         prefs_common.summary_quicksearch_autorun);
603
604         quicksearch->autorun_menuitem = menuitem;
605
606         g_signal_connect(G_OBJECT(menuitem), "activate",
607                          G_CALLBACK(searchtype_autorun_changed),
608                          quicksearch);
609
610         gtk_option_menu_set_menu(GTK_OPTION_MENU(search_type_opt), search_type);
611
612         quicksearch->search_type_opt = search_type_opt;
613         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
614
615         gtk_widget_show(search_type);
616
617         search_string_entry = gtk_combo_new();
618         gtk_box_pack_start(GTK_BOX(hbox_search), search_string_entry, FALSE, FALSE, 2);
619         gtk_combo_set_value_in_list(GTK_COMBO(search_string_entry), FALSE, TRUE);
620         gtk_combo_set_case_sensitive(GTK_COMBO(search_string_entry), TRUE);
621         if (prefs_common.summary_quicksearch_history)
622                 gtk_combo_set_popdown_strings(GTK_COMBO(search_string_entry),
623                         prefs_common.summary_quicksearch_history);
624         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(search_string_entry)->entry), "");
625         gtk_widget_show(search_string_entry);
626
627         search_hbox = gtk_hbox_new(FALSE, 5);
628
629 #if GTK_CHECK_VERSION(2, 8, 0)
630         clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
631 #else
632         clear_search = gtk_button_new_with_label(_(" Clear "));
633 #endif
634         gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
635                            FALSE, FALSE, 0);
636         g_signal_connect(G_OBJECT(clear_search), "clicked",
637                          G_CALLBACK(clear_search_cb), quicksearch);
638         gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
639                              clear_search,
640                              _("Clear the current search"), NULL);
641         gtk_widget_show(clear_search);
642
643 #if GTK_CHECK_VERSION(2, 8, 0)
644         search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
645 #else
646         search_condition_expression = gtk_button_new_with_label(" ... ");
647 #endif
648         gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
649                            FALSE, FALSE, 0);
650         g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
651                          G_CALLBACK(search_condition_expr),
652                          quicksearch);
653         gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
654                              search_condition_expression,
655                              _("Edit search criteria"), NULL);
656         gtk_widget_show(search_condition_expression);
657
658 #if GTK_CHECK_VERSION(2, 8, 0)
659         search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
660 #else
661         search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
662 #endif
663         gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
664                            FALSE, FALSE, 0);
665         g_signal_connect(G_OBJECT(search_description), "clicked",
666                          G_CALLBACK(search_description_cb), NULL);
667         gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
668                              search_description,
669                              _("Information about extended symbols"), NULL);
670         gtk_widget_show(search_description);
671
672         gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
673         gtk_widget_show(search_hbox);
674
675         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
676                            "key_press_event",
677                            G_CALLBACK(searchbar_pressed),
678                            quicksearch);
679
680         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
681                          "changed",
682                          G_CALLBACK(searchbar_changed_cb),
683                          quicksearch);
684
685         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
686                          "focus_in_event",
687                          G_CALLBACK(searchbar_focus_evt_in),
688                          quicksearch);
689         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
690                          "focus_out_event",
691                          G_CALLBACK(searchbar_focus_evt_out),
692                          quicksearch);
693
694         quicksearch->hbox_search = hbox_search;
695         quicksearch->search_type = search_type;
696         quicksearch->search_string_entry = search_string_entry;
697         quicksearch->search_condition_expression = search_condition_expression;
698         quicksearch->search_description = search_description;
699         quicksearch->matcher_list = NULL;
700         quicksearch->active = FALSE;
701         quicksearch->running = FALSE;
702         quicksearch->clear_search = clear_search;
703         quicksearch->in_typing = FALSE;
704         quicksearch->press_timeout_id = -1;
705
706         update_extended_buttons(quicksearch);
707
708         return quicksearch;
709 }
710
711 void quicksearch_relayout(QuickSearch *quicksearch)
712 {
713         switch (prefs_common.layout_mode) {
714         case NORMAL_LAYOUT:
715         case WIDE_LAYOUT:
716         case WIDE_MSGLIST_LAYOUT:
717 #if GTK_CHECK_VERSION(2, 8, 0)
718                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO);
719                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT);
720                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR);
721 #else
722                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _(" Extended Symbols... "));
723                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), " ... ");
724                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _(" Clear "));
725 #endif
726                 break;
727         case VERTICAL_LAYOUT:
728 #if GTK_CHECK_VERSION(2, 8, 0)
729                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), "");
730                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "");
731                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), "");
732
733                 gtk_button_set_image(GTK_BUTTON(quicksearch->search_description),
734                         gtk_image_new_from_stock(GTK_STOCK_INFO, GTK_ICON_SIZE_BUTTON));
735                 gtk_button_set_image(GTK_BUTTON(quicksearch->search_condition_expression),
736                         gtk_image_new_from_stock(GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON));
737                 gtk_button_set_image(GTK_BUTTON(quicksearch->clear_search),
738                         gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
739 #else
740                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _("Info"));
741                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "...");
742                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _("Clear"));
743 #endif
744                 break;
745         }
746 }
747
748 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
749 {
750         return quicksearch->hbox_search;
751 }
752
753 void quicksearch_show(QuickSearch *quicksearch)
754 {
755         MainWindow *mainwin = mainwindow_get_mainwindow();
756         GtkWidget *ctree = NULL;
757         prepare_matcher(quicksearch);
758         gtk_widget_show(quicksearch->hbox_search);
759         update_extended_buttons(quicksearch);
760         gtk_widget_grab_focus(
761                 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
762
763         GTK_EVENTS_FLUSH();
764
765         if (!mainwin || !mainwin->summaryview) {
766                 return;
767         }
768         
769         ctree = summary_get_main_widget(mainwin->summaryview);
770         
771         if (ctree && mainwin->summaryview->selected)
772                 gtk_ctree_node_moveto(GTK_CTREE(ctree), 
773                                 mainwin->summaryview->selected, 
774                                 0, 0.5, 0);
775 }
776
777 void quicksearch_hide(QuickSearch *quicksearch)
778 {
779         if (quicksearch_is_active(quicksearch)) {
780                 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
781                 quicksearch_set_active(quicksearch, FALSE);
782         }
783         gtk_widget_hide(quicksearch->hbox_search);
784 }
785
786 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
787                      const gchar *matchstring)
788 {
789         quicksearch_set_type(quicksearch, type);
790
791         if (!matchstring || !(*matchstring))
792                 quicksearch->in_typing = FALSE;
793
794         g_signal_handlers_block_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
795                         G_CALLBACK(searchbar_changed_cb), quicksearch);
796         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry),
797                            matchstring);
798         g_signal_handlers_unblock_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
799                         G_CALLBACK(searchbar_changed_cb), quicksearch);
800
801         prefs_common.summary_quicksearch_type = type;
802
803         prepare_matcher(quicksearch);
804
805         quicksearch_set_running(quicksearch, TRUE);
806         if (quicksearch->callback != NULL)
807                 quicksearch->callback(quicksearch, quicksearch->callback_data);
808         quicksearch_set_running(quicksearch, FALSE);
809 }
810
811 gboolean quicksearch_is_active(QuickSearch *quicksearch)
812 {
813         return quicksearch->active && 
814                 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
815                  || quicksearch->matcher_list != NULL);
816 }
817
818 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
819 {
820         static GdkColor yellow;
821         static GdkColor red;
822         static GdkColor black;
823         static gboolean colors_initialised = FALSE;
824         gboolean error = FALSE;
825
826         if (!colors_initialised) {
827                 gdk_color_parse("#f5f6be", &yellow);
828                 gdk_color_parse("#000000", &black);
829                 gdk_color_parse("#ff7070", &red);
830                 colors_initialised = gdk_colormap_alloc_color(
831                         gdk_colormap_get_system(), &yellow, FALSE, TRUE);
832                 colors_initialised &= gdk_colormap_alloc_color(
833                         gdk_colormap_get_system(), &black, FALSE, TRUE);
834                 colors_initialised &= gdk_colormap_alloc_color(
835                         gdk_colormap_get_system(), &red, FALSE, TRUE);
836         }
837
838         quicksearch->active = active;
839
840         if (active && 
841                 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
842                  && quicksearch->matcher_list == NULL))
843                 error = TRUE;
844
845         if (active) {
846                 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
847                 if (colors_initialised) {
848                         gtk_widget_modify_base(
849                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
850                                 GTK_STATE_NORMAL, error ? &red : &yellow);
851                         gtk_widget_modify_text(
852                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
853                                 GTK_STATE_NORMAL, &black);
854                 }
855         } else {
856                 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
857                 if (colors_initialised) {
858                         gtk_widget_modify_base(
859                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
860                                 GTK_STATE_NORMAL, NULL);
861                         gtk_widget_modify_text(
862                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
863                                 GTK_STATE_NORMAL, NULL);
864                 }
865         }
866
867         if (!active) {
868                 quicksearch_reset_cur_folder_item(quicksearch);
869         }
870 }
871
872 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
873                                       QuickSearchExecuteCallback callback,
874                                       gpointer data)
875 {
876         quicksearch->callback = callback;
877         quicksearch->callback_data = data;
878 }
879
880 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
881 {
882         gchar *searched_header = NULL;
883         gboolean result = FALSE;
884
885         if (!quicksearch->active)
886                 return TRUE;
887
888         switch (prefs_common.summary_quicksearch_type) {
889         case QUICK_SEARCH_SUBJECT:
890                 searched_header = msginfo->subject;
891                 break;
892         case QUICK_SEARCH_FROM:
893                 searched_header = msginfo->from;
894                 break;
895         case QUICK_SEARCH_TO:
896                 searched_header = msginfo->to;
897                 break;
898         case QUICK_SEARCH_MIXED:
899                 break;
900         case QUICK_SEARCH_EXTENDED:
901                 break;
902         default:
903                 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
904                 break;
905         }
906         quicksearch->matching = TRUE;
907         if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
908             prefs_common.summary_quicksearch_type != QUICK_SEARCH_MIXED &&
909             prefs_common.summary_quicksearch_type != QUICK_SEARCH_TAG &&
910             quicksearch->search_string &&
911             searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
912                 result = TRUE;
913         else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_MIXED &&
914                 quicksearch->search_string && (
915                 (msginfo->to && strcasestr(msginfo->to, quicksearch->search_string) != NULL) ||
916                 (msginfo->from && strcasestr(msginfo->from, quicksearch->search_string) != NULL) ||
917                 (msginfo->subject && strcasestr(msginfo->subject, quicksearch->search_string) != NULL) ||
918                 ((quicksearch->matcher_list != NULL) &&
919                  matcherlist_match(quicksearch->matcher_list, msginfo))  ))
920                 result = TRUE;
921         else if ((quicksearch->matcher_list != NULL) &&
922                  matcherlist_match(quicksearch->matcher_list, msginfo))
923                 result = TRUE;
924
925         quicksearch->matching = FALSE;
926         if (quicksearch->deferred_free) {
927                 prepare_matcher(quicksearch);
928         }
929
930         return result;
931 }
932
933 /* allow Mutt-like patterns in quick search */
934 static gchar *expand_search_string(const gchar *search_string)
935 {
936         int i = 0;
937         gchar term_char, save_char;
938         gchar *cmd_start, *cmd_end;
939         GString *matcherstr;
940         gchar *returnstr = NULL;
941         gchar *copy_str;
942         gboolean casesens, dontmatch;
943         /* list of allowed pattern abbreviations */
944         struct {
945                 gchar           *abbreviated;   /* abbreviation */
946                 gchar           *command;       /* actual matcher command */
947                 gint            numparams;      /* number of params for cmd */
948                 gboolean        qualifier;      /* do we append regexpcase */
949                 gboolean        quotes;         /* do we need quotes */
950         }
951         cmds[] = {
952                 { "a",  "all",                          0,      FALSE,  FALSE },
953                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
954                 { "al", "age_lower",                    1,      FALSE,  FALSE },
955                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
956                 { "B",  "message",                      1,      TRUE,   TRUE  },
957                 { "c",  "cc",                           1,      TRUE,   TRUE  },
958                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
959                 { "D",  "deleted",                      0,      FALSE,  FALSE },
960                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
961                 { "E",  "execute",                      1,      FALSE,  TRUE  },
962                 { "f",  "from",                         1,      TRUE,   TRUE  },
963                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
964                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
965                 { "i",  "header \"Message-ID\"",        1,      TRUE,   TRUE  },
966                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
967                 { "k",  "colorlabel",                   1,      FALSE,  FALSE },
968                 { "L",  "locked",                       0,      FALSE,  FALSE },
969                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
970                 { "N",  "new",                          0,      FALSE,  FALSE },
971                 { "O",  "~new",                         0,      FALSE,  FALSE },
972                 { "r",  "replied",                      0,      FALSE,  FALSE },
973                 { "R",  "~unread",                      0,      FALSE,  FALSE },
974                 { "s",  "subject",                      1,      TRUE,   TRUE  },
975                 { "se", "score_equal",                  1,      FALSE,  FALSE },
976                 { "sg", "score_greater",                1,      FALSE,  FALSE },
977                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
978                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
979                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
980                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
981                 { "t",  "to",                           1,      TRUE,   TRUE  },
982                 { "tg", "tag",                          1,      TRUE,   TRUE  },
983                 { "T",  "marked",                       0,      FALSE,  FALSE },
984                 { "U",  "unread",                       0,      FALSE,  FALSE },
985                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
986                 { "X",  "test",                         1,      FALSE,  FALSE },
987                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
988                 { "&",  "&",                            0,      FALSE,  FALSE },
989                 { "|",  "|",                            0,      FALSE,  FALSE },
990                 { "p",  "partial",                      0,      FALSE,  FALSE },
991                 { NULL, NULL,                           0,      FALSE,  FALSE }
992         };
993
994         if (search_string == NULL)
995                 return NULL;
996
997         copy_str = g_strdup(search_string);
998
999         matcherstr = g_string_sized_new(16);
1000         cmd_start = copy_str;
1001         while (cmd_start && *cmd_start) {
1002                 /* skip all white spaces */
1003                 while (*cmd_start && isspace((guchar)*cmd_start))
1004                         cmd_start++;
1005                 cmd_end = cmd_start;
1006
1007                 /* extract a command */
1008                 while (*cmd_end && !isspace((guchar)*cmd_end))
1009                         cmd_end++;
1010
1011                 /* save character */
1012                 save_char = *cmd_end;
1013                 *cmd_end = '\0';
1014
1015                 dontmatch = FALSE;
1016                 casesens = FALSE;
1017
1018                 /* ~ and ! mean logical NOT */
1019                 if (*cmd_start == '~' || *cmd_start == '!')
1020                 {
1021                         dontmatch = TRUE;
1022                         cmd_start++;
1023                 }
1024                 /* % means case sensitive match */
1025                 if (*cmd_start == '%')
1026                 {
1027                         casesens = TRUE;
1028                         cmd_start++;
1029                 }
1030
1031                 /* find matching abbreviation */
1032                 for (i = 0; cmds[i].command; i++) {
1033                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
1034                                 /* restore character */
1035                                 *cmd_end = save_char;
1036
1037                                 /* copy command */
1038                                 if (matcherstr->len > 0) {
1039                                         g_string_append(matcherstr, " ");
1040                                 }
1041                                 if (dontmatch)
1042                                         g_string_append(matcherstr, "~");
1043                                 g_string_append(matcherstr, cmds[i].command);
1044                                 g_string_append(matcherstr, " ");
1045
1046                                 /* stop if no params required */
1047                                 if (cmds[i].numparams == 0)
1048                                         break;
1049
1050                                 /* extract a parameter, allow quotes */
1051                                 while (*cmd_end && isspace((guchar)*cmd_end))
1052                                         cmd_end++;
1053
1054                                 cmd_start = cmd_end;
1055                                 if (*cmd_start == '"') {
1056                                         term_char = '"';
1057                                         cmd_end++;
1058                                 }
1059                                 else
1060                                         term_char = ' ';
1061
1062                                 /* extract actual parameter */
1063                                 while ((*cmd_end) && (*cmd_end != term_char))
1064                                         cmd_end++;
1065
1066                                 if (*cmd_end == '"')
1067                                         cmd_end++;
1068
1069                                 save_char = *cmd_end;
1070                                 *cmd_end = '\0';
1071
1072                                 if (cmds[i].qualifier) {
1073                                         if (casesens)
1074                                                 g_string_append(matcherstr, "regexp ");
1075                                         else
1076                                                 g_string_append(matcherstr, "regexpcase ");
1077                                 }
1078
1079                                 /* do we need to add quotes ? */
1080                                 if (cmds[i].quotes && term_char != '"')
1081                                         g_string_append(matcherstr, "\"");
1082
1083                                 /* copy actual parameter */
1084                                 g_string_append(matcherstr, cmd_start);
1085
1086                                 /* do we need to add quotes ? */
1087                                 if (cmds[i].quotes && term_char != '"')
1088                                         g_string_append(matcherstr, "\"");
1089
1090                                 /* restore original character */
1091                                 *cmd_end = save_char;
1092
1093                                 break;
1094                         }
1095                 }
1096
1097                 if (*cmd_end)
1098                         cmd_end++;
1099                 cmd_start = cmd_end;
1100         }
1101
1102         g_free(copy_str);
1103
1104         /* return search string if no match is found to allow
1105            all available filtering expressions in quicksearch */
1106         if (matcherstr->len > 0) returnstr = matcherstr->str;
1107         else returnstr = g_strdup(search_string);
1108
1109         g_string_free(matcherstr, FALSE);
1110         return returnstr;
1111 }
1112
1113 static gchar *expand_tag_search_string(const gchar *search_string)
1114 {
1115         gchar *newstr = NULL;
1116         gchar **words = search_string ? g_strsplit(search_string, " ", -1):NULL;
1117         gint i = 0;
1118         while (words && words[i] && *words[i]) {
1119                 g_strstrip(words[i]);
1120                 if (!newstr) {
1121                         newstr = g_strdup_printf("tag regexpcase \"%s\"", words[i]);
1122                 } else {
1123                         gint o_len = strlen(newstr);
1124                         gint s_len = 18; /* strlen("|tag regexpcase \"\"") */
1125                         gint n_len = s_len + strlen(words[i]);
1126                         newstr = g_realloc(newstr,o_len+n_len+1);
1127                         strcpy(newstr+o_len, "|tag regexpcase \"");
1128                         strcpy(newstr+o_len+(s_len-1), words[i]);
1129                         strcpy(newstr+o_len+s_len, "\"");
1130                 }
1131                 i++;
1132         }
1133         g_strfreev(words);
1134         return newstr;
1135 }
1136
1137 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
1138 {
1139         quicksearch->running = run;
1140 }
1141
1142 gboolean quicksearch_is_running(QuickSearch *quicksearch)
1143 {
1144         return quicksearch->running;
1145 }
1146
1147 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
1148 {
1149         GtkEntry *entry = GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry);
1150         glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1151         guint32 c;
1152         char *str = g_strdup(gtk_entry_get_text(entry));
1153         char *begin = str;
1154         char *end = NULL;
1155         char *new = NULL;
1156         char key[7] = "";
1157         guint char_len = 0;
1158
1159         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
1160                 /* remove selection */
1161                 gtk_editable_delete_selection(GTK_EDITABLE(entry));
1162                 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1163                 /* refresh string */
1164                 g_free(str);
1165                 str = g_strdup(gtk_entry_get_text(entry));
1166                 begin = str;
1167         }
1168
1169         if (!(c = gdk_keyval_to_unicode(val))) {
1170                 g_free(str);
1171                 return;
1172         }
1173         char_len = g_unichar_to_utf8(c, key);
1174         if (char_len < 0)
1175                 return;
1176         key[char_len] = '\0';
1177         if (curpos < g_utf8_strlen(str, -1)) {
1178                 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
1179                 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
1180                 *stop = '\0';
1181                 new = g_strdup_printf("%s%s%s", begin, key, end);
1182                 gtk_entry_set_text(entry, new);
1183                 g_free(end);
1184         } else {
1185                 new = g_strdup_printf("%s%s", begin, key);
1186                 gtk_entry_set_text(entry, new);
1187         }
1188         g_free(str);
1189         g_free(new);
1190         gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1191
1192 }
1193
1194 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
1195                                  FolderItem *src)
1196 {
1197         GSList *msglist = NULL;
1198         GSList *cur;
1199         gboolean result = FALSE;
1200         gint num = 0, total = 0;
1201         gint interval = quicksearch_is_fast(quicksearch) ? 5000:100;
1202
1203         statusbar_print_all(_("Searching in %s... \n"),
1204                 src->path ? src->path : "(null)");
1205                 
1206         msglist = folder_item_get_msg_list(src);
1207         total = src->total_msgs;
1208         folder_item_update_freeze();
1209         for (cur = msglist; cur != NULL; cur = cur->next) {
1210                 MsgInfo *msg = (MsgInfo *)cur->data;
1211                 statusbar_progress_all(num++,total, interval);
1212                 if (quicksearch_match(quicksearch, msg)) {
1213                         result = TRUE;
1214                         break;
1215                 }
1216                 if (num % interval == 0)
1217                         GTK_EVENTS_FLUSH();
1218                 if (!quicksearch_is_active(quicksearch))
1219                         break;
1220         }
1221         folder_item_update_thaw();
1222         statusbar_progress_all(0,0,0);
1223         statusbar_pop_all();
1224
1225         procmsg_msg_list_free(msglist);
1226         return result;
1227 }
1228
1229 void quicksearch_search_subfolders(QuickSearch *quicksearch,
1230                                    FolderView *folderview,
1231                                    FolderItem *folder_item)
1232 {
1233         FolderItem *cur = NULL;
1234         GNode *node = folder_item->node->children;
1235
1236         if (!prefs_common.summary_quicksearch_recurse
1237         ||  quicksearch->in_typing == TRUE)
1238                 return;
1239
1240         for (; node != NULL; node = node->next) {
1241                 cur = FOLDER_ITEM(node->data);
1242                 if (quicksearch_match_subfolder(quicksearch, cur)) {
1243                         folderview_update_search_icon(cur, TRUE);
1244                 } else {
1245                         folderview_update_search_icon(cur, FALSE);
1246                 }
1247                 if (cur->node->children)
1248                         quicksearch_search_subfolders(quicksearch,
1249                                                       folderview,
1250                                                       cur);
1251         }
1252         quicksearch->root_folder_item = folder_item;
1253         if (!quicksearch_is_active(quicksearch))
1254                 quicksearch_reset_cur_folder_item(quicksearch);
1255 }
1256
1257 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
1258                                     FolderItem *folder_item)
1259 {
1260         FolderItem *cur = NULL;
1261         GNode *node = (folder_item && folder_item->node) ?
1262                         folder_item->node->children : NULL;
1263
1264         for (; node != NULL; node = node->next) {
1265                 cur = FOLDER_ITEM(node->data);
1266                 folderview_update_search_icon(cur, FALSE);
1267                 if (cur->node->children)
1268                         quicksearch_reset_folder_items(quicksearch,
1269                                                        cur);
1270         }
1271 }
1272
1273 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
1274 {
1275         if (quicksearch->root_folder_item)
1276                 quicksearch_reset_folder_items(quicksearch,
1277                                                quicksearch->root_folder_item);
1278
1279         quicksearch->root_folder_item = NULL;
1280 }
1281
1282 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1283 {
1284         return quicksearch->in_typing;
1285 }