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