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