d41dffd3375821d9a35068d0e9d9dd6ab6154945
[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, FALSE, FALSE, 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 #if GTK_CHECK_VERSION(2, 8, 0)
665         clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
666 #else
667         clear_search = gtk_button_new_with_label(_(" Clear "));
668 #endif
669         gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
670                            FALSE, FALSE, 0);
671         g_signal_connect(G_OBJECT(clear_search), "clicked",
672                          G_CALLBACK(clear_search_cb), quicksearch);
673         CLAWS_SET_TIP(clear_search,
674                              _("Clear the current search"));
675         gtk_widget_show(clear_search);
676
677 #if GTK_CHECK_VERSION(2, 8, 0)
678         search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
679 #else
680         search_condition_expression = gtk_button_new_with_label(" ... ");
681 #endif
682         gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
683                            FALSE, FALSE, 0);
684         g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
685                          G_CALLBACK(search_condition_expr),
686                          quicksearch);
687         CLAWS_SET_TIP(search_condition_expression,
688                              _("Edit search criteria"));
689         gtk_widget_show(search_condition_expression);
690
691 #if GTK_CHECK_VERSION(2, 8, 0)
692         search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
693 #else
694         search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
695 #endif
696         gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
697                            FALSE, FALSE, 0);
698         g_signal_connect(G_OBJECT(search_description), "clicked",
699                          G_CALLBACK(search_description_cb), NULL);
700         CLAWS_SET_TIP(search_description,
701                              _("Information about extended symbols"));
702         gtk_widget_show(search_description);
703
704         gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
705         gtk_widget_show(search_hbox);
706
707         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
708                            "key_press_event",
709                            G_CALLBACK(searchbar_pressed),
710                            quicksearch);
711
712         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
713                          "changed",
714                          G_CALLBACK(searchbar_changed_cb),
715                          quicksearch);
716
717         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
718                          "focus_in_event",
719                          G_CALLBACK(searchbar_focus_evt_in),
720                          quicksearch);
721         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
722                          "focus_out_event",
723                          G_CALLBACK(searchbar_focus_evt_out),
724                          quicksearch);
725
726         quicksearch->hbox_search = hbox_search;
727         quicksearch->search_type = search_type;
728         quicksearch->search_string_entry = search_string_entry;
729         quicksearch->search_condition_expression = search_condition_expression;
730         quicksearch->search_description = search_description;
731         quicksearch->matcher_list = NULL;
732         quicksearch->active = FALSE;
733         quicksearch->running = FALSE;
734         quicksearch->clear_search = clear_search;
735         quicksearch->in_typing = FALSE;
736         quicksearch->press_timeout_id = -1;
737
738         update_extended_buttons(quicksearch);
739
740         return quicksearch;
741 }
742
743 void quicksearch_relayout(QuickSearch *quicksearch)
744 {
745         switch (prefs_common.layout_mode) {
746         case NORMAL_LAYOUT:
747         case WIDE_LAYOUT:
748         case WIDE_MSGLIST_LAYOUT:
749 #if GTK_CHECK_VERSION(2, 8, 0)
750                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO);
751                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT);
752                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR);
753 #else
754                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _(" Extended Symbols... "));
755                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), " ... ");
756                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _(" Clear "));
757 #endif
758                 break;
759         case VERTICAL_LAYOUT:
760 #if GTK_CHECK_VERSION(2, 8, 0)
761                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), "");
762                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "");
763                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), "");
764
765                 gtk_button_set_image(GTK_BUTTON(quicksearch->search_description),
766                         gtk_image_new_from_stock(GTK_STOCK_INFO, GTK_ICON_SIZE_BUTTON));
767                 gtk_button_set_image(GTK_BUTTON(quicksearch->search_condition_expression),
768                         gtk_image_new_from_stock(GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON));
769                 gtk_button_set_image(GTK_BUTTON(quicksearch->clear_search),
770                         gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
771 #else
772                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _("Info"));
773                 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "...");
774                 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _("Clear"));
775 #endif
776                 break;
777         }
778 }
779
780 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
781 {
782         return quicksearch->hbox_search;
783 }
784
785 void quicksearch_show(QuickSearch *quicksearch)
786 {
787         MainWindow *mainwin = mainwindow_get_mainwindow();
788         GtkWidget *ctree = NULL;
789         prepare_matcher(quicksearch);
790         gtk_widget_show(quicksearch->hbox_search);
791         update_extended_buttons(quicksearch);
792         gtk_widget_grab_focus(
793                 GTK_WIDGET(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))));
794
795         GTK_EVENTS_FLUSH();
796
797         if (!mainwin || !mainwin->summaryview) {
798                 return;
799         }
800         
801         ctree = summary_get_main_widget(mainwin->summaryview);
802         
803         if (ctree && mainwin->summaryview->selected)
804                 gtk_cmctree_node_moveto(GTK_CMCTREE(ctree), 
805                                 mainwin->summaryview->selected, 
806                                 0, 0.5, 0);
807 }
808
809 void quicksearch_hide(QuickSearch *quicksearch)
810 {
811         if (quicksearch_is_active(quicksearch)) {
812                 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
813                 quicksearch_set_active(quicksearch, FALSE);
814         }
815         gtk_widget_hide(quicksearch->hbox_search);
816 }
817
818 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
819                      const gchar *matchstring)
820 {
821         quicksearch_set_type(quicksearch, type);
822
823         if (!matchstring || !(*matchstring))
824                 quicksearch->in_typing = FALSE;
825
826         g_signal_handlers_block_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
827                         G_CALLBACK(searchbar_changed_cb), quicksearch);
828         gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
829                            matchstring);
830         g_signal_handlers_unblock_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
831                         G_CALLBACK(searchbar_changed_cb), quicksearch);
832
833         prefs_common.summary_quicksearch_type = type;
834
835         prepare_matcher(quicksearch);
836
837         quicksearch_set_running(quicksearch, TRUE);
838         if (quicksearch->callback != NULL)
839                 quicksearch->callback(quicksearch, quicksearch->callback_data);
840         quicksearch_set_running(quicksearch, FALSE);
841 }
842
843 gboolean quicksearch_is_active(QuickSearch *quicksearch)
844 {
845         return quicksearch->active && 
846                 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
847                  || quicksearch->matcher_list != NULL);
848 }
849
850 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
851 {
852         static GdkColor yellow;
853         static GdkColor red;
854         static GdkColor black;
855         static gboolean colors_initialised = FALSE;
856         gboolean error = FALSE;
857
858         if (!colors_initialised) {
859                 gdk_color_parse("#f5f6be", &yellow);
860                 gdk_color_parse("#000000", &black);
861                 gdk_color_parse("#ff7070", &red);
862                 colors_initialised = gdk_colormap_alloc_color(
863                         gdk_colormap_get_system(), &yellow, FALSE, TRUE);
864                 colors_initialised &= gdk_colormap_alloc_color(
865                         gdk_colormap_get_system(), &black, FALSE, TRUE);
866                 colors_initialised &= gdk_colormap_alloc_color(
867                         gdk_colormap_get_system(), &red, FALSE, TRUE);
868         }
869
870         quicksearch->active = active;
871
872         if (active && 
873                 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
874                  && quicksearch->matcher_list == NULL))
875                 error = TRUE;
876
877         if (active) {
878                 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
879                 if (colors_initialised) {
880                         gtk_widget_modify_base(
881                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
882                                 GTK_STATE_NORMAL, error ? &red : &yellow);
883                         gtk_widget_modify_text(
884                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
885                                 GTK_STATE_NORMAL, &black);
886                 }
887         } else {
888                 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
889                 if (colors_initialised) {
890                         gtk_widget_modify_base(
891                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
892                                 GTK_STATE_NORMAL, NULL);
893                         gtk_widget_modify_text(
894                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
895                                 GTK_STATE_NORMAL, NULL);
896                 }
897         }
898
899         if (!active) {
900                 quicksearch_reset_cur_folder_item(quicksearch);
901         }
902 }
903
904 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
905                                       QuickSearchExecuteCallback callback,
906                                       gpointer data)
907 {
908         quicksearch->callback = callback;
909         quicksearch->callback_data = data;
910 }
911
912 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
913 {
914         gchar *searched_header = NULL;
915         gboolean result = FALSE;
916         gchar *to = NULL, *from = NULL, *subject = NULL;
917
918         if (!quicksearch->active)
919                 return TRUE;
920
921         switch (prefs_common.summary_quicksearch_type) {
922         case QUICK_SEARCH_SUBJECT:
923                 if (msginfo->subject)
924                         searched_header = g_utf8_casefold(msginfo->subject, -1);
925                 else
926                         return FALSE;
927                 break;
928         case QUICK_SEARCH_FROM:
929                 if (msginfo->from)
930                         searched_header = g_utf8_casefold(msginfo->from, -1);
931                 else
932                         return FALSE;
933                 break;
934         case QUICK_SEARCH_TO:
935                 if (msginfo->to)
936                         searched_header = g_utf8_casefold(msginfo->to, -1);
937                 else
938                         return FALSE;
939                 break;
940         case QUICK_SEARCH_MIXED:
941                 if (msginfo->to)
942                         to = g_utf8_casefold(msginfo->to, -1);
943                 if (msginfo->from)
944                         from = g_utf8_casefold(msginfo->from, -1);
945                 if (msginfo->subject)
946                         subject = g_utf8_casefold(msginfo->subject, -1);
947                 break;
948         case QUICK_SEARCH_EXTENDED:
949                 break;
950         default:
951                 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
952                 break;
953         }
954
955         quicksearch->matching = TRUE;
956         if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
957             prefs_common.summary_quicksearch_type != QUICK_SEARCH_MIXED &&
958             prefs_common.summary_quicksearch_type != QUICK_SEARCH_TAG &&
959             quicksearch->search_string &&
960             searched_header && strstr(searched_header, quicksearch->search_string) != NULL)
961                 result = TRUE;
962         else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_MIXED &&
963                 quicksearch->search_string && (
964                 (to && strstr(to, quicksearch->search_string) != NULL) ||
965                 (from && strstr(from, quicksearch->search_string) != NULL) ||
966                 (subject && strstr(subject, quicksearch->search_string) != NULL) ||
967                 ((quicksearch->matcher_list != NULL) &&
968                  matcherlist_match(quicksearch->matcher_list, msginfo))  ))
969                 result = TRUE;
970         else if ((quicksearch->matcher_list != NULL) &&
971                  matcherlist_match(quicksearch->matcher_list, msginfo))
972                 result = TRUE;
973
974         quicksearch->matching = FALSE;
975         if (quicksearch->deferred_free) {
976                 prepare_matcher(quicksearch);
977         }
978
979         g_free(to);
980         g_free(from);
981         g_free(subject);
982         g_free(searched_header);
983
984         return result;
985 }
986
987 /* allow Mutt-like patterns in quick search */
988 static gchar *expand_search_string(const gchar *search_string)
989 {
990         int i = 0;
991         gchar term_char, save_char;
992         gchar *cmd_start, *cmd_end;
993         GString *matcherstr;
994         gchar *returnstr = NULL;
995         gchar *copy_str;
996         gboolean casesens, dontmatch;
997         /* list of allowed pattern abbreviations */
998         struct {
999                 gchar           *abbreviated;   /* abbreviation */
1000                 gchar           *command;       /* actual matcher command */
1001                 gint            numparams;      /* number of params for cmd */
1002                 gboolean        qualifier;      /* do we append regexpcase */
1003                 gboolean        quotes;         /* do we need quotes */
1004         }
1005         cmds[] = {
1006                 { "a",  "all",                          0,      FALSE,  FALSE },
1007                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
1008                 { "al", "age_lower",                    1,      FALSE,  FALSE },
1009                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
1010                 { "B",  "message",                      1,      TRUE,   TRUE  },
1011                 { "c",  "cc",                           1,      TRUE,   TRUE  },
1012                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
1013                 { "D",  "deleted",                      0,      FALSE,  FALSE },
1014                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
1015                 { "E",  "execute",                      1,      FALSE,  TRUE  },
1016                 { "f",  "from",                         1,      TRUE,   TRUE  },
1017                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
1018                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
1019                 { "i",  "header \"Message-ID\"",        1,      TRUE,   TRUE  },
1020                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
1021                 { "k",  "colorlabel",                   1,      FALSE,  FALSE },
1022                 { "L",  "locked",                       0,      FALSE,  FALSE },
1023                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
1024                 { "N",  "new",                          0,      FALSE,  FALSE },
1025                 { "O",  "~new",                         0,      FALSE,  FALSE },
1026                 { "r",  "replied",                      0,      FALSE,  FALSE },
1027                 { "R",  "~unread",                      0,      FALSE,  FALSE },
1028                 { "s",  "subject",                      1,      TRUE,   TRUE  },
1029                 { "se", "score_equal",                  1,      FALSE,  FALSE },
1030                 { "sg", "score_greater",                1,      FALSE,  FALSE },
1031                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
1032                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
1033                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
1034                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
1035                 { "t",  "to",                           1,      TRUE,   TRUE  },
1036                 { "tg", "tag",                          1,      TRUE,   TRUE  },
1037                 { "T",  "marked",                       0,      FALSE,  FALSE },
1038                 { "U",  "unread",                       0,      FALSE,  FALSE },
1039                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
1040                 { "X",  "test",                         1,      FALSE,  FALSE },
1041                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
1042                 { "&",  "&",                            0,      FALSE,  FALSE },
1043                 { "|",  "|",                            0,      FALSE,  FALSE },
1044                 { "p",  "partial",                      0,      FALSE,  FALSE },
1045                 { NULL, NULL,                           0,      FALSE,  FALSE }
1046         };
1047
1048         if (search_string == NULL)
1049                 return NULL;
1050
1051         copy_str = g_strdup(search_string);
1052
1053         matcherstr = g_string_sized_new(16);
1054         cmd_start = copy_str;
1055         while (cmd_start && *cmd_start) {
1056                 /* skip all white spaces */
1057                 while (*cmd_start && isspace((guchar)*cmd_start))
1058                         cmd_start++;
1059                 cmd_end = cmd_start;
1060
1061                 /* extract a command */
1062                 while (*cmd_end && !isspace((guchar)*cmd_end))
1063                         cmd_end++;
1064
1065                 /* save character */
1066                 save_char = *cmd_end;
1067                 *cmd_end = '\0';
1068
1069                 dontmatch = FALSE;
1070                 casesens = FALSE;
1071
1072                 /* ~ and ! mean logical NOT */
1073                 if (*cmd_start == '~' || *cmd_start == '!')
1074                 {
1075                         dontmatch = TRUE;
1076                         cmd_start++;
1077                 }
1078                 /* % means case sensitive match */
1079                 if (*cmd_start == '%')
1080                 {
1081                         casesens = TRUE;
1082                         cmd_start++;
1083                 }
1084
1085                 /* find matching abbreviation */
1086                 for (i = 0; cmds[i].command; i++) {
1087                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
1088                                 /* restore character */
1089                                 *cmd_end = save_char;
1090
1091                                 /* copy command */
1092                                 if (matcherstr->len > 0) {
1093                                         g_string_append(matcherstr, " ");
1094                                 }
1095                                 if (dontmatch)
1096                                         g_string_append(matcherstr, "~");
1097                                 g_string_append(matcherstr, cmds[i].command);
1098                                 g_string_append(matcherstr, " ");
1099
1100                                 /* stop if no params required */
1101                                 if (cmds[i].numparams == 0)
1102                                         break;
1103
1104                                 /* extract a parameter, allow quotes */
1105                                 while (*cmd_end && isspace((guchar)*cmd_end))
1106                                         cmd_end++;
1107
1108                                 cmd_start = cmd_end;
1109                                 if (*cmd_start == '"') {
1110                                         term_char = '"';
1111                                         cmd_end++;
1112                                 }
1113                                 else
1114                                         term_char = ' ';
1115
1116                                 /* extract actual parameter */
1117                                 while ((*cmd_end) && (*cmd_end != term_char))
1118                                         cmd_end++;
1119
1120                                 if (*cmd_end == '"')
1121                                         cmd_end++;
1122
1123                                 save_char = *cmd_end;
1124                                 *cmd_end = '\0';
1125
1126                                 if (cmds[i].qualifier) {
1127                                         if (casesens)
1128                                                 g_string_append(matcherstr, "regexp ");
1129                                         else
1130                                                 g_string_append(matcherstr, "regexpcase ");
1131                                 }
1132
1133                                 /* do we need to add quotes ? */
1134                                 if (cmds[i].quotes && term_char != '"')
1135                                         g_string_append(matcherstr, "\"");
1136
1137                                 /* copy actual parameter */
1138                                 g_string_append(matcherstr, cmd_start);
1139
1140                                 /* do we need to add quotes ? */
1141                                 if (cmds[i].quotes && term_char != '"')
1142                                         g_string_append(matcherstr, "\"");
1143
1144                                 /* restore original character */
1145                                 *cmd_end = save_char;
1146
1147                                 break;
1148                         }
1149                 }
1150
1151                 if (*cmd_end)
1152                         cmd_end++;
1153                 cmd_start = cmd_end;
1154         }
1155
1156         g_free(copy_str);
1157
1158         /* return search string if no match is found to allow
1159            all available filtering expressions in quicksearch */
1160         if (matcherstr->len > 0) returnstr = matcherstr->str;
1161         else returnstr = g_strdup(search_string);
1162
1163         g_string_free(matcherstr, FALSE);
1164         return returnstr;
1165 }
1166
1167 static gchar *expand_tag_search_string(const gchar *search_string)
1168 {
1169         gchar *newstr = NULL;
1170         gchar **words = search_string ? g_strsplit(search_string, " ", -1):NULL;
1171         gint i = 0;
1172         while (words && words[i] && *words[i]) {
1173                 g_strstrip(words[i]);
1174                 if (!newstr) {
1175                         newstr = g_strdup_printf("tag regexpcase \"%s\"", words[i]);
1176                 } else {
1177                         gint o_len = strlen(newstr);
1178                         gint s_len = 18; /* strlen("|tag regexpcase \"\"") */
1179                         gint n_len = s_len + strlen(words[i]);
1180                         newstr = g_realloc(newstr,o_len+n_len+1);
1181                         strcpy(newstr+o_len, "|tag regexpcase \"");
1182                         strcpy(newstr+o_len+(s_len-1), words[i]);
1183                         strcpy(newstr+o_len+(n_len-1), "\"");
1184                 }
1185                 i++;
1186         }
1187         g_strfreev(words);
1188         return newstr;
1189 }
1190
1191 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
1192 {
1193         quicksearch->running = run;
1194 }
1195
1196 gboolean quicksearch_is_running(QuickSearch *quicksearch)
1197 {
1198         return quicksearch->running;
1199 }
1200
1201 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
1202 {
1203         GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))));
1204         glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1205         guint32 c;
1206         char *str = g_strdup(gtk_entry_get_text(entry));
1207         char *begin = str;
1208         char *end = NULL;
1209         char *new = NULL;
1210         char key[7] = "";
1211         guint char_len = 0;
1212
1213         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
1214                 /* remove selection */
1215                 gtk_editable_delete_selection(GTK_EDITABLE(entry));
1216                 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1217                 /* refresh string */
1218                 g_free(str);
1219                 str = g_strdup(gtk_entry_get_text(entry));
1220                 begin = str;
1221         }
1222
1223         if (!(c = gdk_keyval_to_unicode(val))) {
1224                 g_free(str);
1225                 return;
1226         }
1227         char_len = g_unichar_to_utf8(c, key);
1228         if (char_len < 0)
1229                 return;
1230         key[char_len] = '\0';
1231         if (curpos < g_utf8_strlen(str, -1)) {
1232                 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
1233                 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
1234                 *stop = '\0';
1235                 new = g_strdup_printf("%s%s%s", begin, key, end);
1236                 gtk_entry_set_text(entry, new);
1237                 g_free(end);
1238         } else {
1239                 new = g_strdup_printf("%s%s", begin, key);
1240                 gtk_entry_set_text(entry, new);
1241         }
1242         g_free(str);
1243         g_free(new);
1244         gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1245
1246 }
1247
1248 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
1249                                  FolderItem *src)
1250 {
1251         GSList *msglist = NULL;
1252         GSList *cur;
1253         gboolean result = FALSE;
1254         gint num = 0, total = 0;
1255         gint interval = quicksearch_is_fast(quicksearch) ? 5000:100;
1256
1257         statusbar_print_all(_("Searching in %s... \n"),
1258                 src->path ? src->path : "(null)");
1259                 
1260         msglist = folder_item_get_msg_list(src);
1261         total = src->total_msgs;
1262         folder_item_update_freeze();
1263         for (cur = msglist; cur != NULL; cur = cur->next) {
1264                 MsgInfo *msg = (MsgInfo *)cur->data;
1265                 statusbar_progress_all(num++,total, interval);
1266                 if (quicksearch_match(quicksearch, msg)) {
1267                         result = TRUE;
1268                         break;
1269                 }
1270                 if (num % interval == 0)
1271                         GTK_EVENTS_FLUSH();
1272                 if (!quicksearch_is_active(quicksearch))
1273                         break;
1274         }
1275         folder_item_update_thaw();
1276         statusbar_progress_all(0,0,0);
1277         statusbar_pop_all();
1278
1279         procmsg_msg_list_free(msglist);
1280         return result;
1281 }
1282
1283 gboolean quicksearch_is_in_subfolder(QuickSearch *quicksearch, FolderItem *cur)
1284 {
1285         if (quicksearch->root_folder_item == NULL)
1286                 return FALSE;
1287         
1288         while (cur) {
1289                 if (cur == quicksearch->root_folder_item) {
1290                         return TRUE;
1291                 }
1292                 cur = folder_item_parent(cur);
1293         }
1294         return FALSE;
1295 }
1296
1297 void quicksearch_search_subfolders(QuickSearch *quicksearch,
1298                                    FolderView *folderview,
1299                                    FolderItem *folder_item)
1300 {
1301         FolderItem *cur = NULL;
1302         GNode *node = folder_item->node->children;
1303
1304         if (!prefs_common.summary_quicksearch_recurse
1305         ||  quicksearch->in_typing == TRUE)
1306                 return;
1307
1308         for (; node != NULL; node = node->next) {
1309                 cur = FOLDER_ITEM(node->data);
1310                 if (quicksearch_match_subfolder(quicksearch, cur)) {
1311                         folderview_update_search_icon(cur, TRUE);
1312                 } else {
1313                         folderview_update_search_icon(cur, FALSE);
1314                 }
1315                 if (cur->node->children)
1316                         quicksearch_search_subfolders(quicksearch,
1317                                                       folderview,
1318                                                       cur);
1319         }
1320         quicksearch->root_folder_item = folder_item;
1321         if (!quicksearch_is_active(quicksearch))
1322                 quicksearch_reset_cur_folder_item(quicksearch);
1323 }
1324
1325 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
1326                                     FolderItem *folder_item)
1327 {
1328         FolderItem *cur = NULL;
1329         GNode *node = (folder_item && folder_item->node) ?
1330                         folder_item->node->children : NULL;
1331
1332         for (; node != NULL; node = node->next) {
1333                 cur = FOLDER_ITEM(node->data);
1334                 folderview_update_search_icon(cur, FALSE);
1335                 if (cur->node->children)
1336                         quicksearch_reset_folder_items(quicksearch,
1337                                                        cur);
1338         }
1339 }
1340
1341 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
1342 {
1343         if (quicksearch->root_folder_item)
1344                 quicksearch_reset_folder_items(quicksearch,
1345                                                quicksearch->root_folder_item);
1346
1347         quicksearch->root_folder_item = NULL;
1348 }
1349
1350 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1351 {
1352         return quicksearch->in_typing;
1353 }