2007-05-10 [wwp] 2.9.2cvs5
[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         g_signal_connect(G_OBJECT(menuitem), "activate",
575                          G_CALLBACK(searchtype_dynamic_changed),
576                          quicksearch);
577
578         menuitem = gtk_check_menu_item_new_with_label(_("Run on select"));
579         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
580
581         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
582                                         prefs_common.summary_quicksearch_autorun);
583
584         quicksearch->autorun_menuitem = menuitem;
585
586         g_signal_connect(G_OBJECT(menuitem), "activate",
587                          G_CALLBACK(searchtype_autorun_changed),
588                          quicksearch);
589
590         gtk_option_menu_set_menu(GTK_OPTION_MENU(search_type_opt), search_type);
591
592         quicksearch->search_type_opt = search_type_opt;
593         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
594
595         gtk_widget_show(search_type);
596
597         search_string_entry = gtk_combo_new();
598         gtk_box_pack_start(GTK_BOX(hbox_search), search_string_entry, FALSE, FALSE, 2);
599         gtk_combo_set_value_in_list(GTK_COMBO(search_string_entry), FALSE, TRUE);
600         gtk_combo_set_case_sensitive(GTK_COMBO(search_string_entry), TRUE);
601         if (prefs_common.summary_quicksearch_history)
602                 gtk_combo_set_popdown_strings(GTK_COMBO(search_string_entry),
603                         prefs_common.summary_quicksearch_history);
604         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(search_string_entry)->entry), "");
605         gtk_widget_show(search_string_entry);
606
607         search_hbox = gtk_hbox_new(FALSE, 5);
608
609 #if GTK_CHECK_VERSION(2, 8, 0)
610         clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
611 #else
612         clear_search = gtk_button_new_with_label(_(" Clear "));
613 #endif
614         gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
615                            FALSE, FALSE, 0);
616         g_signal_connect(G_OBJECT(clear_search), "clicked",
617                          G_CALLBACK(clear_search_cb), quicksearch);
618         gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
619                              clear_search,
620                              _("Clear the current search"), NULL);
621         gtk_widget_show(clear_search);
622
623 #if GTK_CHECK_VERSION(2, 8, 0)
624         search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
625 #else
626         search_condition_expression = gtk_button_new_with_label(" ... ");
627 #endif
628         gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
629                            FALSE, FALSE, 0);
630         g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
631                          G_CALLBACK(search_condition_expr),
632                          quicksearch);
633         gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
634                              search_condition_expression,
635                              _("Edit search criteria"), NULL);
636         gtk_widget_show(search_condition_expression);
637
638 #if GTK_CHECK_VERSION(2, 8, 0)
639         search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
640 #else
641         search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
642 #endif
643         gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
644                            FALSE, FALSE, 0);
645         g_signal_connect(G_OBJECT(search_description), "clicked",
646                          G_CALLBACK(search_description_cb), NULL);
647         gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
648                              search_description,
649                              _("Information about extended symbols"), NULL);
650         gtk_widget_show(search_description);
651
652         gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
653         gtk_widget_show(search_hbox);
654
655         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
656                            "key_press_event",
657                            G_CALLBACK(searchbar_pressed),
658                            quicksearch);
659
660         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
661                          "changed",
662                          G_CALLBACK(searchbar_changed_cb),
663                          quicksearch);
664
665         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
666                          "focus_in_event",
667                          G_CALLBACK(searchbar_focus_evt_in),
668                          quicksearch);
669         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
670                          "focus_out_event",
671                          G_CALLBACK(searchbar_focus_evt_out),
672                          quicksearch);
673
674         quicksearch->hbox_search = hbox_search;
675         quicksearch->search_type = search_type;
676         quicksearch->search_string_entry = search_string_entry;
677         quicksearch->search_condition_expression = search_condition_expression;
678         quicksearch->search_description = search_description;
679         quicksearch->matcher_list = NULL;
680         quicksearch->active = FALSE;
681         quicksearch->running = FALSE;
682         quicksearch->clear_search = clear_search;
683         quicksearch->in_typing = FALSE;
684         quicksearch->press_timeout_id = -1;
685
686         update_extended_buttons(quicksearch);
687
688         return quicksearch;
689 }
690
691 void quicksearch_relayout(QuickSearch *quicksearch)
692 {
693         switch (prefs_common.layout_mode) {
694         case NORMAL_LAYOUT:
695         case WIDE_LAYOUT:
696         case WIDE_MSGLIST_LAYOUT:
697 #if GTK_CHECK_VERSION(2, 8, 0)
698                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO);
699                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT);
700                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR);
701 #else
702                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _(" Extended Symbols... "));
703                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), " ... ");
704                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _(" Clear "));
705 #endif
706                 break;
707         case VERTICAL_LAYOUT:
708 #if GTK_CHECK_VERSION(2, 8, 0)
709                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), "");
710                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "");
711                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), "");
712
713                 gtk_button_set_image(GTK_BUTTON(quicksearch->search_description),
714                         gtk_image_new_from_stock(GTK_STOCK_INFO, GTK_ICON_SIZE_BUTTON));
715                 gtk_button_set_image(GTK_BUTTON(quicksearch->search_condition_expression),
716                         gtk_image_new_from_stock(GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON));
717                 gtk_button_set_image(GTK_BUTTON(quicksearch->clear_search),
718                         gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
719 #else
720                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _("Info"));
721                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "...");
722                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _("Clear"));
723 #endif
724                 break;
725         }
726 }
727
728 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
729 {
730         return quicksearch->hbox_search;
731 }
732
733 void quicksearch_show(QuickSearch *quicksearch)
734 {
735         MainWindow *mainwin = mainwindow_get_mainwindow();
736         GtkWidget *ctree = NULL;
737         prepare_matcher(quicksearch);
738         gtk_widget_show(quicksearch->hbox_search);
739         update_extended_buttons(quicksearch);
740         gtk_widget_grab_focus(
741                 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
742
743         GTK_EVENTS_FLUSH();
744
745         if (!mainwin || !mainwin->summaryview) {
746                 return;
747         }
748         
749         ctree = summary_get_main_widget(mainwin->summaryview);
750         
751         if (ctree && mainwin->summaryview->selected)
752                 gtk_ctree_node_moveto(GTK_CTREE(ctree), 
753                                 mainwin->summaryview->selected, 
754                                 0, 0.5, 0);
755 }
756
757 void quicksearch_hide(QuickSearch *quicksearch)
758 {
759         if (quicksearch_is_active(quicksearch)) {
760                 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
761                 quicksearch_set_active(quicksearch, FALSE);
762         }
763         gtk_widget_hide(quicksearch->hbox_search);
764 }
765
766 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
767                      const gchar *matchstring)
768 {
769         quicksearch_set_type(quicksearch, type);
770
771         if (!matchstring || !(*matchstring))
772                 quicksearch->in_typing = FALSE;
773
774         g_signal_handlers_block_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
775                         G_CALLBACK(searchbar_changed_cb), quicksearch);
776         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry),
777                            matchstring);
778         g_signal_handlers_unblock_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
779                         G_CALLBACK(searchbar_changed_cb), quicksearch);
780
781         prefs_common.summary_quicksearch_type = type;
782
783         prepare_matcher(quicksearch);
784
785         quicksearch_set_running(quicksearch, TRUE);
786         if (quicksearch->callback != NULL)
787                 quicksearch->callback(quicksearch, quicksearch->callback_data);
788         quicksearch_set_running(quicksearch, FALSE);
789 }
790
791 gboolean quicksearch_is_active(QuickSearch *quicksearch)
792 {
793         return quicksearch->active && 
794                 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
795                  || quicksearch->matcher_list != NULL);
796 }
797
798 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
799 {
800         static GdkColor yellow;
801         static GdkColor red;
802         static GdkColor black;
803         static gboolean colors_initialised = FALSE;
804         gboolean error = FALSE;
805
806         if (!colors_initialised) {
807                 gdk_color_parse("#f5f6be", &yellow);
808                 gdk_color_parse("#000000", &black);
809                 gdk_color_parse("#ff7070", &red);
810                 colors_initialised = gdk_colormap_alloc_color(
811                         gdk_colormap_get_system(), &yellow, FALSE, TRUE);
812                 colors_initialised &= gdk_colormap_alloc_color(
813                         gdk_colormap_get_system(), &black, FALSE, TRUE);
814                 colors_initialised &= gdk_colormap_alloc_color(
815                         gdk_colormap_get_system(), &red, FALSE, TRUE);
816         }
817
818         quicksearch->active = active;
819
820         if (active && 
821                 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
822                  && quicksearch->matcher_list == NULL))
823                 error = TRUE;
824
825         if (active) {
826                 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
827                 if (colors_initialised) {
828                         gtk_widget_modify_base(
829                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
830                                 GTK_STATE_NORMAL, error ? &red : &yellow);
831                         gtk_widget_modify_text(
832                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
833                                 GTK_STATE_NORMAL, &black);
834                 }
835         } else {
836                 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
837                 if (colors_initialised) {
838                         gtk_widget_modify_base(
839                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
840                                 GTK_STATE_NORMAL, NULL);
841                         gtk_widget_modify_text(
842                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
843                                 GTK_STATE_NORMAL, NULL);
844                 }
845         }
846
847         if (!active) {
848                 quicksearch_reset_cur_folder_item(quicksearch);
849         }
850 }
851
852 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
853                                       QuickSearchExecuteCallback callback,
854                                       gpointer data)
855 {
856         quicksearch->callback = callback;
857         quicksearch->callback_data = data;
858 }
859
860 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
861 {
862         gchar *searched_header = NULL;
863         gboolean result = FALSE;
864
865         if (!quicksearch->active)
866                 return TRUE;
867
868         switch (prefs_common.summary_quicksearch_type) {
869         case QUICK_SEARCH_SUBJECT:
870                 searched_header = msginfo->subject;
871                 break;
872         case QUICK_SEARCH_FROM:
873                 searched_header = msginfo->from;
874                 break;
875         case QUICK_SEARCH_TO:
876                 searched_header = msginfo->to;
877                 break;
878         case QUICK_SEARCH_MIXED:
879                 break;
880         case QUICK_SEARCH_EXTENDED:
881                 break;
882         default:
883                 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
884                 break;
885         }
886         quicksearch->matching = TRUE;
887         if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
888             prefs_common.summary_quicksearch_type != QUICK_SEARCH_MIXED &&
889             quicksearch->search_string &&
890             searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
891                 result = TRUE;
892         else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_MIXED &&
893                 quicksearch->search_string && (
894                 (msginfo->to && strcasestr(msginfo->to, quicksearch->search_string) != NULL) ||
895                 (msginfo->from && strcasestr(msginfo->from, quicksearch->search_string) != NULL) ||
896                 (msginfo->subject && strcasestr(msginfo->subject, quicksearch->search_string) != NULL)  ))
897                 result = TRUE;
898         else if ((quicksearch->matcher_list != NULL) &&
899                  matcherlist_match(quicksearch->matcher_list, msginfo))
900                 result = TRUE;
901
902         quicksearch->matching = FALSE;
903         if (quicksearch->deferred_free) {
904                 prepare_matcher(quicksearch);
905         }
906
907         return result;
908 }
909
910 /* allow Mutt-like patterns in quick search */
911 static gchar *expand_search_string(const gchar *search_string)
912 {
913         int i = 0;
914         gchar term_char, save_char;
915         gchar *cmd_start, *cmd_end;
916         GString *matcherstr;
917         gchar *returnstr = NULL;
918         gchar *copy_str;
919         gboolean casesens, dontmatch;
920         /* list of allowed pattern abbreviations */
921         struct {
922                 gchar           *abbreviated;   /* abbreviation */
923                 gchar           *command;       /* actual matcher command */
924                 gint            numparams;      /* number of params for cmd */
925                 gboolean        qualifier;      /* do we append regexpcase */
926                 gboolean        quotes;         /* do we need quotes */
927         }
928         cmds[] = {
929                 { "a",  "all",                          0,      FALSE,  FALSE },
930                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
931                 { "al", "age_lower",                    1,      FALSE,  FALSE },
932                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
933                 { "B",  "message",                      1,      TRUE,   TRUE  },
934                 { "c",  "cc",                           1,      TRUE,   TRUE  },
935                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
936                 { "D",  "deleted",                      0,      FALSE,  FALSE },
937                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
938                 { "E",  "execute",                      1,      FALSE,  TRUE  },
939                 { "f",  "from",                         1,      TRUE,   TRUE  },
940                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
941                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
942                 { "i",  "header \"Message-ID\"",        1,      TRUE,   TRUE  },
943                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
944                 { "k",  "colorlabel",                   1,      FALSE,  FALSE },
945                 { "L",  "locked",                       0,      FALSE,  FALSE },
946                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
947                 { "N",  "new",                          0,      FALSE,  FALSE },
948                 { "O",  "~new",                         0,      FALSE,  FALSE },
949                 { "r",  "replied",                      0,      FALSE,  FALSE },
950                 { "R",  "~unread",                      0,      FALSE,  FALSE },
951                 { "s",  "subject",                      1,      TRUE,   TRUE  },
952                 { "se", "score_equal",                  1,      FALSE,  FALSE },
953                 { "sg", "score_greater",                1,      FALSE,  FALSE },
954                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
955                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
956                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
957                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
958                 { "t",  "to",                           1,      TRUE,   TRUE  },
959                 { "T",  "marked",                       0,      FALSE,  FALSE },
960                 { "U",  "unread",                       0,      FALSE,  FALSE },
961                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
962                 { "X",  "test",                         1,      FALSE,  FALSE },
963                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
964                 { "&",  "&",                            0,      FALSE,  FALSE },
965                 { "|",  "|",                            0,      FALSE,  FALSE },
966                 { "p",  "partial",                      0,      FALSE,  FALSE },
967                 { NULL, NULL,                           0,      FALSE,  FALSE }
968         };
969
970         if (search_string == NULL)
971                 return NULL;
972
973         copy_str = g_strdup(search_string);
974
975         matcherstr = g_string_sized_new(16);
976         cmd_start = copy_str;
977         while (cmd_start && *cmd_start) {
978                 /* skip all white spaces */
979                 while (*cmd_start && isspace((guchar)*cmd_start))
980                         cmd_start++;
981                 cmd_end = cmd_start;
982
983                 /* extract a command */
984                 while (*cmd_end && !isspace((guchar)*cmd_end))
985                         cmd_end++;
986
987                 /* save character */
988                 save_char = *cmd_end;
989                 *cmd_end = '\0';
990
991                 dontmatch = FALSE;
992                 casesens = FALSE;
993
994                 /* ~ and ! mean logical NOT */
995                 if (*cmd_start == '~' || *cmd_start == '!')
996                 {
997                         dontmatch = TRUE;
998                         cmd_start++;
999                 }
1000                 /* % means case sensitive match */
1001                 if (*cmd_start == '%')
1002                 {
1003                         casesens = TRUE;
1004                         cmd_start++;
1005                 }
1006
1007                 /* find matching abbreviation */
1008                 for (i = 0; cmds[i].command; i++) {
1009                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
1010                                 /* restore character */
1011                                 *cmd_end = save_char;
1012
1013                                 /* copy command */
1014                                 if (matcherstr->len > 0) {
1015                                         g_string_append(matcherstr, " ");
1016                                 }
1017                                 if (dontmatch)
1018                                         g_string_append(matcherstr, "~");
1019                                 g_string_append(matcherstr, cmds[i].command);
1020                                 g_string_append(matcherstr, " ");
1021
1022                                 /* stop if no params required */
1023                                 if (cmds[i].numparams == 0)
1024                                         break;
1025
1026                                 /* extract a parameter, allow quotes */
1027                                 while (*cmd_end && isspace((guchar)*cmd_end))
1028                                         cmd_end++;
1029
1030                                 cmd_start = cmd_end;
1031                                 if (*cmd_start == '"') {
1032                                         term_char = '"';
1033                                         cmd_end++;
1034                                 }
1035                                 else
1036                                         term_char = ' ';
1037
1038                                 /* extract actual parameter */
1039                                 while ((*cmd_end) && (*cmd_end != term_char))
1040                                         cmd_end++;
1041
1042                                 if (*cmd_end == '"')
1043                                         cmd_end++;
1044
1045                                 save_char = *cmd_end;
1046                                 *cmd_end = '\0';
1047
1048                                 if (cmds[i].qualifier) {
1049                                         if (casesens)
1050                                                 g_string_append(matcherstr, "regexp ");
1051                                         else
1052                                                 g_string_append(matcherstr, "regexpcase ");
1053                                 }
1054
1055                                 /* do we need to add quotes ? */
1056                                 if (cmds[i].quotes && term_char != '"')
1057                                         g_string_append(matcherstr, "\"");
1058
1059                                 /* copy actual parameter */
1060                                 g_string_append(matcherstr, cmd_start);
1061
1062                                 /* do we need to add quotes ? */
1063                                 if (cmds[i].quotes && term_char != '"')
1064                                         g_string_append(matcherstr, "\"");
1065
1066                                 /* restore original character */
1067                                 *cmd_end = save_char;
1068
1069                                 break;
1070                         }
1071                 }
1072
1073                 if (*cmd_end)
1074                         cmd_end++;
1075                 cmd_start = cmd_end;
1076         }
1077
1078         g_free(copy_str);
1079
1080         /* return search string if no match is found to allow
1081            all available filtering expressions in quicksearch */
1082         if (matcherstr->len > 0) returnstr = matcherstr->str;
1083         else returnstr = g_strdup(search_string);
1084
1085         g_string_free(matcherstr, FALSE);
1086         return returnstr;
1087 }
1088
1089 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
1090 {
1091         quicksearch->running = run;
1092 }
1093
1094 gboolean quicksearch_is_running(QuickSearch *quicksearch)
1095 {
1096         return quicksearch->running;
1097 }
1098
1099 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
1100 {
1101         GtkEntry *entry = GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry);
1102         glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1103         guint32 c;
1104         char *str = g_strdup(gtk_entry_get_text(entry));
1105         char *begin = str;
1106         char *end = NULL;
1107         char *new = NULL;
1108         char key[7] = "";
1109         guint char_len = 0;
1110
1111         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
1112                 /* remove selection */
1113                 gtk_editable_delete_selection(GTK_EDITABLE(entry));
1114                 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1115                 /* refresh string */
1116                 g_free(str);
1117                 str = g_strdup(gtk_entry_get_text(entry));
1118                 begin = str;
1119         }
1120
1121         if (!(c = gdk_keyval_to_unicode(val))) {
1122                 g_free(str);
1123                 return;
1124         }
1125         char_len = g_unichar_to_utf8(c, key);
1126         if (char_len < 0)
1127                 return;
1128         key[char_len] = '\0';
1129         if (curpos < g_utf8_strlen(str, -1)) {
1130                 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
1131                 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
1132                 *stop = '\0';
1133                 new = g_strdup_printf("%s%s%s", begin, key, end);
1134                 gtk_entry_set_text(entry, new);
1135                 g_free(end);
1136         } else {
1137                 new = g_strdup_printf("%s%s", begin, key);
1138                 gtk_entry_set_text(entry, new);
1139         }
1140         g_free(str);
1141         g_free(new);
1142         gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1143
1144 }
1145
1146 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
1147                                  FolderItem *src)
1148 {
1149         GSList *msglist = NULL;
1150         GSList *cur;
1151         gboolean result = FALSE;
1152         gint num = 0, total = 0;
1153         gint interval = quicksearch_is_fast(quicksearch) ? 5000:100;
1154
1155         statusbar_print_all(_("Searching in %s... \n"),
1156                 src->path ? src->path : "(null)");
1157                 
1158         msglist = folder_item_get_msg_list(src);
1159         total = src->total_msgs;
1160         folder_item_update_freeze();
1161         for (cur = msglist; cur != NULL; cur = cur->next) {
1162                 MsgInfo *msg = (MsgInfo *)cur->data;
1163                 statusbar_progress_all(num++,total, interval);
1164                 if (quicksearch_match(quicksearch, msg)) {
1165                         result = TRUE;
1166                         break;
1167                 }
1168                 if (num % interval == 0)
1169                         GTK_EVENTS_FLUSH();
1170                 if (!quicksearch_is_active(quicksearch))
1171                         break;
1172         }
1173         folder_item_update_thaw();
1174         statusbar_progress_all(0,0,0);
1175         statusbar_pop_all();
1176
1177         procmsg_msg_list_free(msglist);
1178         return result;
1179 }
1180
1181 void quicksearch_search_subfolders(QuickSearch *quicksearch,
1182                                    FolderView *folderview,
1183                                    FolderItem *folder_item)
1184 {
1185         FolderItem *cur = NULL;
1186         GNode *node = folder_item->node->children;
1187
1188         if (!prefs_common.summary_quicksearch_recurse
1189         ||  quicksearch->in_typing == TRUE)
1190                 return;
1191
1192         for (; node != NULL; node = node->next) {
1193                 cur = FOLDER_ITEM(node->data);
1194                 if (quicksearch_match_subfolder(quicksearch, cur)) {
1195                         folderview_update_search_icon(cur, TRUE);
1196                 } else {
1197                         folderview_update_search_icon(cur, FALSE);
1198                 }
1199                 if (cur->node->children)
1200                         quicksearch_search_subfolders(quicksearch,
1201                                                       folderview,
1202                                                       cur);
1203         }
1204         quicksearch->root_folder_item = folder_item;
1205         if (!quicksearch_is_active(quicksearch))
1206                 quicksearch_reset_cur_folder_item(quicksearch);
1207 }
1208
1209 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
1210                                     FolderItem *folder_item)
1211 {
1212         FolderItem *cur = NULL;
1213         GNode *node = (folder_item && folder_item->node) ?
1214                         folder_item->node->children : NULL;
1215
1216         for (; node != NULL; node = node->next) {
1217                 cur = FOLDER_ITEM(node->data);
1218                 folderview_update_search_icon(cur, FALSE);
1219                 if (cur->node->children)
1220                         quicksearch_reset_folder_items(quicksearch,
1221                                                        cur);
1222         }
1223 }
1224
1225 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
1226 {
1227         if (quicksearch->root_folder_item)
1228                 quicksearch_reset_folder_items(quicksearch,
1229                                                quicksearch->root_folder_item);
1230
1231         quicksearch->root_folder_item = NULL;
1232 }
1233
1234 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1235 {
1236         return quicksearch->in_typing;
1237 }