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