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